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
63
64 from CedarBackup2.filesystem import FilesystemList
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", ]
86
87
88
89
90
91
182
183
184
185
186
187
263
264
265
266
267
268
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
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, unittest=False):
417 """
418 Initializes a CD writer object.
419
420 The current user must have write access to the device at the time the
421 object is instantiated, or an exception will be thrown. However, no
422 media-related validation is done, and in fact there is no need for any
423 media to be in the drive until one of the other media attribute-related
424 methods is called.
425
426 The various instance variables such as C{deviceType}, C{deviceVendor},
427 etc. might be C{None}, if we're unable to parse this specific information
428 from the C{cdrecord} output. This information is just for reference.
429
430 The SCSI id is optional, but the device path is required. If the SCSI id
431 is passed in, then the hardware id attribute will be taken from the SCSI
432 id. Otherwise, the hardware id will be taken from the device.
433
434 If cdrecord improperly detects whether your writer device has a tray and
435 can be safely opened and closed, then pass in C{noEject=False}. This
436 will override the properties and the device will never be ejected.
437
438 @note: The C{unittest} parameter should never be set to C{True}
439 outside of Cedar Backup code. It is intended for use in unit testing
440 Cedar Backup internals and has no other sensible purpose.
441
442 @param device: Filesystem device associated with this writer.
443 @type device: Absolute path to a filesystem device, i.e. C{/dev/cdrw}
444
445 @param scsiId: SCSI id for the device (optional).
446 @type scsiId: If provided, SCSI id in the form C{[<method>:]scsibus,target,lun}
447
448 @param driveSpeed: Speed at which the drive writes.
449 @type driveSpeed: Use C{2} for 2x device, etc. or C{None} to use device default.
450
451 @param mediaType: Type of the media that is assumed to be in the drive.
452 @type mediaType: One of the valid media type as discussed above.
453
454 @param noEject: Overrides properties to indicate that the device does not support eject.
455 @type noEject: Boolean true/false
456
457 @param unittest: Turns off certain validations, for use in unit testing.
458 @type unittest: Boolean true/false
459
460 @raise ValueError: If the device is not valid for some reason.
461 @raise ValueError: If the SCSI id is not in a valid form.
462 @raise ValueError: If the drive speed is not an integer >= 1.
463 @raise IOError: If device properties could not be read for some reason.
464 """
465 self._image = None
466 self._device = validateDevice(device, unittest)
467 self._scsiId = validateScsiId(scsiId)
468 self._driveSpeed = validateDriveSpeed(driveSpeed)
469 self._media = MediaDefinition(mediaType)
470 self._noEject = noEject
471 if not unittest:
472 (self._deviceType,
473 self._deviceVendor,
474 self._deviceId,
475 self._deviceBufferSize,
476 self._deviceSupportsMulti,
477 self._deviceHasTray,
478 self._deviceCanEject) = self._retrieveProperties()
479
480
481
482
483
484
486 """
487 Property target used to get the device value.
488 """
489 return self._device
490
492 """
493 Property target used to get the SCSI id value.
494 """
495 return self._scsiId
496
498 """
499 Property target used to get the hardware id value.
500 """
501 if self._scsiId is None:
502 return self._device
503 return self._scsiId
504
506 """
507 Property target used to get the drive speed.
508 """
509 return self._driveSpeed
510
516
518 """
519 Property target used to get the device type.
520 """
521 return self._deviceType
522
524 """
525 Property target used to get the device vendor.
526 """
527 return self._deviceVendor
528
530 """
531 Property target used to get the device id.
532 """
533 return self._deviceId
534
536 """
537 Property target used to get the device buffer size.
538 """
539 return self._deviceBufferSize
540
542 """
543 Property target used to get the device-support-multi flag.
544 """
545 return self._deviceSupportsMulti
546
548 """
549 Property target used to get the device-has-tray flag.
550 """
551 return self._deviceHasTray
552
554 """
555 Property target used to get the device-can-eject flag.
556 """
557 return self._deviceCanEject
558
559 device = property(_getDevice, None, None, doc="Filesystem device name for this writer.")
560 scsiId = property(_getScsiId, None, None, doc="SCSI id for the device, in the form C{[<method>:]scsibus,target,lun}.")
561 hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer, either SCSI id or device path.");
562 driveSpeed = property(_getDriveSpeed, None, None, doc="Speed at which the drive writes.")
563 media = property(_getMedia, None, None, doc="Definition of media that is expected to be in the device.")
564 deviceType = property(_getDeviceType, None, None, doc="Type of the device, as returned from C{cdrecord -prcap}.")
565 deviceVendor = property(_getDeviceVendor, None, None, doc="Vendor of the device, as returned from C{cdrecord -prcap}.")
566 deviceId = property(_getDeviceId, None, None, doc="Device identification, as returned from C{cdrecord -prcap}.")
567 deviceBufferSize = property(_getDeviceBufferSize, None, None, doc="Size of the device's write buffer, in bytes.")
568 deviceSupportsMulti = property(_getDeviceSupportsMulti, None, None, doc="Indicates whether device supports multisession discs.")
569 deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.")
570 deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.")
571
572
573
574
575
576
578 """Indicates whether the media is rewritable per configuration."""
579 return self._media.rewritable
580
582 """
583 Retrieves properties for a device from C{cdrecord}.
584
585 The results are returned as a tuple of the object device attributes as
586 returned from L{_parsePropertiesOutput}: C{(deviceType, deviceVendor,
587 deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray,
588 deviceCanEject)}.
589
590 @return: Results tuple as described above.
591 @raise IOError: If there is a problem talking to the device.
592 """
593 args = CdWriter._buildPropertiesArgs(self.hardwareId)
594 command = resolveCommand(CDRECORD_COMMAND)
595 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
596 if result != 0:
597 raise IOError("Error (%d) executing cdrecord command to get properties." % result)
598 return CdWriter._parsePropertiesOutput(output)
599
601 """
602 Retrieves capacity for the current media in terms of a C{MediaCapacity}
603 object.
604
605 If C{entireDisc} is passed in as C{True} the capacity will be for the
606 entire disc, as if it were to be rewritten from scratch. If the drive
607 does not support writing multisession discs or if C{useMulti} is passed
608 in as C{False}, the capacity will also be as if the disc were to be
609 rewritten from scratch, but the indicated boundaries value will be
610 C{None}. The same will happen if the disc cannot be read for some
611 reason. Otherwise, the capacity (including the boundaries) will
612 represent whatever space remains on the disc to be filled by future
613 sessions.
614
615 @param entireDisc: Indicates whether to return capacity for entire disc.
616 @type entireDisc: Boolean true/false
617
618 @param useMulti: Indicates whether a multisession disc should be assumed, if possible.
619 @type useMulti: Boolean true/false
620
621 @return: C{MediaCapacity} object describing the capacity of the media.
622 @raise IOError: If the media could not be read for some reason.
623 """
624 boundaries = self._getBoundaries(entireDisc, useMulti)
625 return CdWriter._calculateCapacity(self._media, boundaries)
626
628 """
629 Gets the ISO boundaries for the media.
630
631 If C{entireDisc} is passed in as C{True} the boundaries will be C{None},
632 as if the disc were to be rewritten from scratch. If the drive does not
633 support writing multisession discs, the returned value will be C{None}.
634 The same will happen if the disc can't be read for some reason.
635 Otherwise, the returned value will be represent the boundaries of the
636 disc's current contents.
637
638 The results are returned as a tuple of (lower, upper) as needed by the
639 C{IsoImage} class. Note that these values are in terms of ISO sectors,
640 not bytes. Clients should generally consider the boundaries value
641 opaque, however.
642
643 @param entireDisc: Indicates whether to return capacity for entire disc.
644 @type entireDisc: Boolean true/false
645
646 @param useMulti: Indicates whether a multisession disc should be assumed, if possible.
647 @type useMulti: Boolean true/false
648
649 @return: Boundaries tuple or C{None}, as described above.
650 @raise IOError: If the media could not be read for some reason.
651 """
652 if not self._deviceSupportsMulti:
653 logger.debug("Device does not support multisession discs; returning boundaries None.")
654 return None
655 elif not useMulti:
656 logger.debug("Use multisession flag is False; returning boundaries None.")
657 return None
658 elif entireDisc:
659 logger.debug("Entire disc flag is True; returning boundaries None.")
660 return None
661 else:
662 args = CdWriter._buildBoundariesArgs(self.hardwareId)
663 command = resolveCommand(CDRECORD_COMMAND)
664 (result, output) = executeCommand(command, args, returnOutput=True, ignoreStderr=True)
665 if result != 0:
666 logger.debug("Error (%d) executing cdrecord command to get capacity." % result)
667 logger.warn("Unable to read disc (might not be initialized); returning boundaries of None.")
668 return None
669 boundaries = CdWriter._parseBoundariesOutput(output)
670 if boundaries is None:
671 logger.debug("Returning disc boundaries: None")
672 else:
673 logger.debug("Returning disc boundaries: (%d, %d)" % (boundaries[0], boundaries[1]))
674 return boundaries
675
677 """
678 Calculates capacity for the media in terms of boundaries.
679
680 If C{boundaries} is C{None} or the lower bound is 0 (zero), then the
681 capacity will be for the entire disc minus the initial lead in.
682 Otherwise, capacity will be as if the caller wanted to add an additional
683 session to the end of the existing data on the disc.
684
685 @param media: MediaDescription object describing the media capacity.
686 @param boundaries: Session boundaries as returned from L{_getBoundaries}.
687
688 @return: C{MediaCapacity} object describing the capacity of the media.
689 """
690 if boundaries is None or boundaries[1] == 0:
691 logger.debug("Capacity calculations are based on a complete disc rewrite.")
692 sectorsAvailable = media.capacity - media.initialLeadIn
693 if sectorsAvailable < 0: sectorsAvailable = 0
694 bytesUsed = 0
695 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES)
696 else:
697 logger.debug("Capacity calculations are based on a new ISO session.")
698 sectorsAvailable = media.capacity - boundaries[1] - media.leadIn
699 if sectorsAvailable < 0: sectorsAvailable = 0
700 bytesUsed = convertSize(boundaries[1], UNIT_SECTORS, UNIT_BYTES)
701 bytesAvailable = convertSize(sectorsAvailable, UNIT_SECTORS, UNIT_BYTES)
702 logger.debug("Used [%s], available [%s]." % (displayBytes(bytesUsed), displayBytes(bytesAvailable)))
703 return MediaCapacity(bytesUsed, bytesAvailable, boundaries)
704 _calculateCapacity = staticmethod(_calculateCapacity)
705
706
707
708
709
710
712 """
713 Initializes the writer's associated ISO image.
714
715 This method initializes the C{image} instance variable so that the caller
716 can use the C{addImageEntry} method. Once entries have been added, the
717 C{writeImage} method can be called with no arguments.
718
719 @param newDisc: Indicates whether the disc should be re-initialized
720 @type newDisc: Boolean true/false.
721
722 @param tmpdir: Temporary directory to use if needed
723 @type tmpdir: String representing a directory path on disk
724
725 @param mediaLabel: Media label to be applied to the image, if any
726 @type mediaLabel: String, no more than 25 characters long
727 """
728 self._image = _ImageProperties()
729 self._image.newDisc = newDisc
730 self._image.tmpdir = encodePath(tmpdir)
731 self._image.mediaLabel = mediaLabel
732 self._image.entries = {}
733
734 - def addImageEntry(self, path, graftPoint):
735 """
736 Adds a filepath entry to the writer's associated ISO image.
737
738 The contents of the filepath -- but not the path itself -- will be added
739 to the image at the indicated graft point. If you don't want to use a
740 graft point, just pass C{None}.
741
742 @note: Before calling this method, you must call L{initializeImage}.
743
744 @param path: File or directory to be added to the image
745 @type path: String representing a path on disk
746
747 @param graftPoint: Graft point to be used when adding this entry
748 @type graftPoint: String representing a graft point path, as described above
749
750 @raise ValueError: If initializeImage() was not previously called
751 """
752 if self._image is None:
753 raise ValueError("Must call initializeImage() before using this method.")
754 if not os.path.exists(path):
755 raise ValueError("Path [%s] does not exist." % path)
756 self._image.entries[path] = graftPoint
757
759 """
760 Resets (overrides) the newDisc flag on the internal image.
761 @param newDisc: New disc flag to set
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 self._image.newDisc = newDisc
767
769 """
770 Gets the estimated size of the image associated with the writer.
771 @return: Estimated size of the image, in bytes.
772 @raise IOError: If there is a problem calling C{mkisofs}.
773 @raise ValueError: If initializeImage() was not previously called
774 """
775 if self._image is None:
776 raise ValueError("Must call initializeImage() before using this method.")
777 image = IsoImage()
778 for path in self._image.entries.keys():
779 image.addEntry(path, self._image.entries[path], override=False, contentsOnly=True)
780 return image.getEstimatedSize()
781
782
783
784
785
786
788 """
789 Opens the device's tray and leaves it open.
790
791 This only works if the device has a tray and supports ejecting its media.
792 We have no way to know if the tray is currently open or closed, so we
793 just send the appropriate command and hope for the best. If the device
794 does not have a tray or does not support ejecting its media, then we do
795 nothing.
796
797 If the writer was constructed with C{noEject=True}, then this is a no-op.
798
799 @raise IOError: If there is an error talking to the device.
800 """
801 if not self._noEject:
802 if self._deviceHasTray and self._deviceCanEject:
803 args = CdWriter._buildOpenTrayArgs(self._device)
804 command = resolveCommand(EJECT_COMMAND)
805 result = executeCommand(command, args)[0]
806 if result != 0:
807 raise IOError("Error (%d) executing eject command to open tray." % result)
808
810 """
811 Closes the device's tray.
812
813 This only works if the device has a tray and supports ejecting its media.
814 We have no way to know if the tray is currently open or closed, so we
815 just send the appropriate command and hope for the best. If the device
816 does not have a tray or does not support ejecting its media, then we do
817 nothing.
818
819 If the writer was constructed with C{noEject=True}, then this is a no-op.
820
821 @raise IOError: If there is an error talking to the device.
822 """
823 if not self._noEject:
824 if self._deviceHasTray and self._deviceCanEject:
825 args = CdWriter._buildCloseTrayArgs(self._device)
826 command = resolveCommand(EJECT_COMMAND)
827 result = executeCommand(command, args)[0]
828 if result != 0:
829 raise IOError("Error (%d) executing eject command to close tray." % result)
830
850
851 - def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
852 """
853 Writes an ISO image to the media in the device.
854
855 If C{newDisc} is passed in as C{True}, we assume that the entire disc
856 will be overwritten, and the media will be blanked before writing it if
857 possible (i.e. if the media is rewritable).
858
859 If C{writeMulti} is passed in as C{True}, then a multisession disc will
860 be written if possible (i.e. if the drive supports writing multisession
861 discs).
862
863 if C{imagePath} is passed in as C{None}, then the existing image
864 configured with C{initializeImage} will be used. Under these
865 circumstances, the passed-in C{newDisc} flag will be ignored.
866
867 By default, we assume that the disc can be written multisession and that
868 we should append to the current contents of the disc. In any case, the
869 ISO image must be generated appropriately (i.e. must take into account
870 any existing session boundaries, etc.)
871
872 @param imagePath: Path to an ISO image on disk, or C{None} to use writer's image
873 @type imagePath: String representing a path on disk
874
875 @param newDisc: Indicates whether the entire disc will overwritten.
876 @type newDisc: Boolean true/false.
877
878 @param writeMulti: Indicates whether a multisession disc should be written, if possible.
879 @type writeMulti: Boolean true/false
880
881 @raise ValueError: If the image path is not absolute.
882 @raise ValueError: If some path cannot be encoded properly.
883 @raise IOError: If the media could not be written to for some reason.
884 @raise ValueError: If no image is passed in and initializeImage() was not previously called
885 """
886 if imagePath is None:
887 if self._image is None:
888 raise ValueError("Must call initializeImage() before using this method with no image path.")
889 try:
890 imagePath = self._createImage()
891 self._writeImage(imagePath, writeMulti, self._image.newDisc)
892 finally:
893 if imagePath is not None and os.path.exists(imagePath):
894 try: os.unlink(imagePath)
895 except: pass
896 else:
897 imagePath = encodePath(imagePath)
898 if not os.path.isabs(imagePath):
899 raise ValueError("Image path must be absolute.")
900 self._writeImage(imagePath, writeMulti, newDisc)
901
903 """
904 Creates an ISO image based on configuration in self._image.
905 @return: Path to the newly-created ISO image on disk.
906 @raise IOError: If there is an error writing the image to disk.
907 @raise ValueError: If there are no filesystem entries in the image
908 @raise ValueError: If a path cannot be encoded properly.
909 """
910 path = None
911 capacity = self.retrieveCapacity(entireDisc=self._image.newDisc)
912 image = IsoImage(self.device, capacity.boundaries)
913 image.volumeId = self._image.mediaLabel
914 for path in self._image.entries.keys():
915 image.addEntry(path, self._image.entries[path], override=False, contentsOnly=True)
916 size = image.getEstimatedSize()
917 logger.info("Image size will be %s." % displayBytes(size))
918 available = capacity.bytesAvailable
919 logger.debug("Media capacity: %s" % displayBytes(available))
920 if size > available:
921 logger.error("Image [%s] does not fit in available capacity [%s]." % (displayBytes(size), displayBytes(available)))
922 raise IOError("Media does not contain enough capacity to store image.")
923 try:
924 (handle, path) = tempfile.mkstemp(dir=self._image.tmpdir)
925 try: os.close(handle)
926 except: pass
927 image.writeImage(path)
928 logger.debug("Completed creating image [%s]." % path)
929 return path
930 except Exception, e:
931 if path is not None and os.path.exists(path):
932 try: os.unlink(path)
933 except: pass
934 raise e
935
936 - def _writeImage(self, imagePath, writeMulti, newDisc):
937 """
938 Write an ISO image to disc using cdrecord.
939 The disc is blanked first if C{newDisc} is C{True}.
940 @param imagePath: Path to an ISO image on disk
941 @param writeMulti: Indicates whether a multisession disc should be written, if possible.
942 @param newDisc: Indicates whether the entire disc will overwritten.
943 """
944 if newDisc:
945 self._blankMedia()
946 args = CdWriter._buildWriteArgs(self.hardwareId, imagePath, self._driveSpeed, writeMulti and self._deviceSupportsMulti)
947 command = resolveCommand(CDRECORD_COMMAND)
948 result = executeCommand(command, args)[0]
949 if result != 0:
950 raise IOError("Error (%d) executing command to write disc." % result)
951 self.refreshMedia()
952
965
966
967
968
969
970
972 """
973 Parses the output from a C{cdrecord} properties command.
974
975 The C{output} parameter should be a list of strings as returned from
976 C{executeCommand} for a C{cdrecord} command with arguments as from
977 C{_buildPropertiesArgs}. The list of strings will be parsed to yield
978 information about the properties of the device.
979
980 The output is expected to be a huge long list of strings. Unfortunately,
981 the strings aren't in a completely regular format. However, the format
982 of individual lines seems to be regular enough that we can look for
983 specific values. Two kinds of parsing take place: one kind of parsing
984 picks out out specific values like the device id, device vendor, etc.
985 The other kind of parsing just sets a boolean flag C{True} if a matching
986 line is found. All of the parsing is done with regular expressions.
987
988 Right now, pretty much nothing in the output is required and we should
989 parse an empty document successfully (albeit resulting in a device that
990 can't eject, doesn't have a tray and doesnt't support multisession
991 discs). I had briefly considered erroring out if certain lines weren't
992 found or couldn't be parsed, but that seems like a bad idea given that
993 most of the information is just for reference.
994
995 The results are returned as a tuple of the object device attributes:
996 C{(deviceType, deviceVendor, deviceId, deviceBufferSize,
997 deviceSupportsMulti, deviceHasTray, deviceCanEject)}.
998
999 @param output: Output from a C{cdrecord -prcap} command.
1000
1001 @return: Results tuple as described above.
1002 @raise IOError: If there is problem parsing the output.
1003 """
1004 deviceType = None
1005 deviceVendor = None
1006 deviceId = None
1007 deviceBufferSize = None
1008 deviceSupportsMulti = False
1009 deviceHasTray = False
1010 deviceCanEject = False
1011 typePattern = re.compile(r"(^Device type\s*:\s*)(.*)(\s*)(.*$)")
1012 vendorPattern = re.compile(r"(^Vendor_info\s*:\s*'\s*)(.*?)(\s*')(.*$)")
1013 idPattern = re.compile(r"(^Identifikation\s*:\s*'\s*)(.*?)(\s*')(.*$)")
1014 bufferPattern = re.compile(r"(^\s*Buffer size in KB:\s*)(.*?)(\s*$)")
1015 multiPattern = re.compile(r"^\s*Does read multi-session.*$")
1016 trayPattern = re.compile(r"^\s*Loading mechanism type: tray.*$")
1017 ejectPattern = re.compile(r"^\s*Does support ejection.*$")
1018 for line in output:
1019 if typePattern.search(line):
1020 deviceType = typePattern.search(line).group(2)
1021 logger.info("Device type is [%s]." % deviceType)
1022 elif vendorPattern.search(line):
1023 deviceVendor = vendorPattern.search(line).group(2)
1024 logger.info("Device vendor is [%s]." % deviceVendor)
1025 elif idPattern.search(line):
1026 deviceId = idPattern.search(line).group(2)
1027 logger.info("Device id is [%s]." % deviceId)
1028 elif bufferPattern.search(line):
1029 try:
1030 sectors = int(bufferPattern.search(line).group(2))
1031 deviceBufferSize = convertSize(sectors, UNIT_KBYTES, UNIT_BYTES)
1032 logger.info("Device buffer size is [%d] bytes." % deviceBufferSize)
1033 except TypeError: pass
1034 elif multiPattern.search(line):
1035 deviceSupportsMulti = True
1036 logger.info("Device does support multisession discs.")
1037 elif trayPattern.search(line):
1038 deviceHasTray = True
1039 logger.info("Device has a tray.")
1040 elif ejectPattern.search(line):
1041 deviceCanEject = True
1042 logger.info("Device can eject its media.")
1043 return (deviceType, deviceVendor, deviceId, deviceBufferSize, deviceSupportsMulti, deviceHasTray, deviceCanEject)
1044 _parsePropertiesOutput = staticmethod(_parsePropertiesOutput)
1045
1047 """
1048 Parses the output from a C{cdrecord} capacity command.
1049
1050 The C{output} parameter should be a list of strings as returned from
1051 C{executeCommand} for a C{cdrecord} command with arguments as from
1052 C{_buildBoundaryArgs}. The list of strings will be parsed to yield
1053 information about the capacity of the media in the device.
1054
1055 Basically, we expect the list of strings to include just one line, a pair
1056 of values. There isn't supposed to be whitespace, but we allow it anyway
1057 in the regular expression. Any lines below the one line we parse are
1058 completely ignored. It would be a good idea to ignore C{stderr} when
1059 executing the C{cdrecord} command that generates output for this method,
1060 because sometimes C{cdrecord} spits out kernel warnings about the actual
1061 output.
1062
1063 The results are returned as a tuple of (lower, upper) as needed by the
1064 C{IsoImage} class. Note that these values are in terms of ISO sectors,
1065 not bytes. Clients should generally consider the boundaries value
1066 opaque, however.
1067
1068 @note: If the boundaries output can't be parsed, we return C{None}.
1069
1070 @param output: Output from a C{cdrecord -msinfo} command.
1071
1072 @return: Boundaries tuple as described above.
1073 @raise IOError: If there is problem parsing the output.
1074 """
1075 if len(output) < 1:
1076 logger.warn("Unable to read disc (might not be initialized); returning full capacity.")
1077 return None
1078 boundaryPattern = re.compile(r"(^\s*)([0-9]*)(\s*,\s*)([0-9]*)(\s*$)")
1079 parsed = boundaryPattern.search(output[0])
1080 if not parsed:
1081 raise IOError("Unable to parse output of boundaries command.")
1082 try:
1083 boundaries = ( int(parsed.group(2)), int(parsed.group(4)) )
1084 except TypeError:
1085 raise IOError("Unable to parse output of boundaries command.")
1086 return boundaries
1087 _parseBoundariesOutput = staticmethod(_parseBoundariesOutput)
1088
1089
1090
1091
1092
1093
1095 """
1096 Builds a list of arguments to be passed to a C{eject} command.
1097
1098 The arguments will cause the C{eject} command to open the tray and
1099 eject the media. No validation is done by this method as to whether
1100 this action actually makes sense.
1101
1102 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
1103
1104 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1105 """
1106 args = []
1107 args.append(device)
1108 return args
1109 _buildOpenTrayArgs = staticmethod(_buildOpenTrayArgs)
1110
1112 """
1113 Builds a list of arguments to be passed to a C{eject} command.
1114
1115 The arguments will cause the C{eject} command to close the tray and reload
1116 the media. No validation is done by this method as to whether this
1117 action actually makes sense.
1118
1119 @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
1120
1121 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1122 """
1123 args = []
1124 args.append("-t")
1125 args.append(device)
1126 return args
1127 _buildCloseTrayArgs = staticmethod(_buildCloseTrayArgs)
1128
1130 """
1131 Builds a list of arguments to be passed to a C{cdrecord} command.
1132
1133 The arguments will cause the C{cdrecord} command to ask the device
1134 for a list of its capacities via the C{-prcap} switch.
1135
1136 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1137
1138 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1139 """
1140 args = []
1141 args.append("-prcap")
1142 args.append("dev=%s" % hardwareId)
1143 return args
1144 _buildPropertiesArgs = staticmethod(_buildPropertiesArgs)
1145
1147 """
1148 Builds a list of arguments to be passed to a C{cdrecord} command.
1149
1150 The arguments will cause the C{cdrecord} command to ask the device for
1151 the current multisession boundaries of the media using the C{-msinfo}
1152 switch.
1153
1154 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1155
1156 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1157 """
1158 args = []
1159 args.append("-msinfo")
1160 args.append("dev=%s" % hardwareId)
1161 return args
1162 _buildBoundariesArgs = staticmethod(_buildBoundariesArgs)
1163
1165 """
1166 Builds a list of arguments to be passed to a C{cdrecord} command.
1167
1168 The arguments will cause the C{cdrecord} command to blank the media in
1169 the device identified by C{hardwareId}. No validation is done by this method
1170 as to whether the action makes sense (i.e. to whether the media even can
1171 be blanked).
1172
1173 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1174 @param driveSpeed: Speed at which the drive writes.
1175
1176 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1177 """
1178 args = []
1179 args.append("-v")
1180 args.append("blank=fast")
1181 if driveSpeed is not None:
1182 args.append("speed=%d" % driveSpeed)
1183 args.append("dev=%s" % hardwareId)
1184 return args
1185 _buildBlankArgs = staticmethod(_buildBlankArgs)
1186
1187 - def _buildWriteArgs(hardwareId, imagePath, driveSpeed=None, writeMulti=True):
1188 """
1189 Builds a list of arguments to be passed to a C{cdrecord} command.
1190
1191 The arguments will cause the C{cdrecord} command to write the indicated
1192 ISO image (C{imagePath}) to the media in the device identified by
1193 C{hardwareId}. The C{writeMulti} argument controls whether to write a
1194 multisession disc. No validation is done by this method as to whether
1195 the action makes sense (i.e. to whether the device even can write
1196 multisession discs, for instance).
1197
1198 @param hardwareId: Hardware id for the device (either SCSI id or device path)
1199 @param imagePath: Path to an ISO image on disk.
1200 @param driveSpeed: Speed at which the drive writes.
1201 @param writeMulti: Indicates whether to write a multisession disc.
1202
1203 @return: List suitable for passing to L{util.executeCommand} as C{args}.
1204 """
1205 args = []
1206 args.append("-v")
1207 if driveSpeed is not None:
1208 args.append("speed=%d" % driveSpeed)
1209 args.append("dev=%s" % hardwareId)
1210 if writeMulti:
1211 args.append("-multi")
1212 args.append("-data")
1213 args.append(imagePath)
1214 return args
1215 _buildWriteArgs = staticmethod(_buildWriteArgs)
1216