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