1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCD.cc
14 #include <sys/ioctl.h>
15 #include <linux/cdrom.h>
23 #include "zypp/target/hal/HalContext.h"
27 #include <cstring> // strerror
28 #include <cstdlib> // getenv
31 #include "zypp/base/Logger.h"
32 #include "zypp/ExternalProgram.h"
33 #include "zypp/media/Mount.h"
34 #include "zypp/media/MediaCD.h"
35 #include "zypp/media/MediaManager.h"
37 #include "zypp/AutoDispose.h"
42 ** if to throw exception on eject errors or ignore them
44 #define REPORT_EJECT_ERRORS 0
47 ** If defined to the full path of the eject utility,
48 ** it will be used additionally to the eject-ioctl.
50 #define EJECT_TOOL_PATH "/bin/eject"
55 //////////////////////////////////////////////////////////////////
58 //////////////////////////////////////////////////////////////////
62 //////////////////////////////////////////////////////////////////
65 typedef std::list<MediaSource> DeviceList;
67 //////////////////////////////////////////////////////////////////
68 /// \brief Try to detect cd/dvd devices using hal/udev
70 /// Returns an empty device list on error.
72 /// \todo I took the code more or less as it was from MediaCD::detectDevices
73 /// into this function. Semantic between HAL and UDEV seems to be slightly
74 /// different, esp. in supportingDVD mode. This should be investigated and fixed.
75 //////////////////////////////////////////////////////////////////
76 DeviceList systemDetectDevices( bool supportingDVD_r )
81 // http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/index.html
82 zypp::AutoDispose<struct udev *> udev( ::udev_new(), ::udev_unref );
85 ERR << "Can't create udev context." << endl;
89 zypp::AutoDispose<struct udev_enumerate *> enumerate( ::udev_enumerate_new(udev), ::udev_enumerate_unref );
92 ERR << "Can't create udev list entry." << endl;
96 ::udev_enumerate_add_match_subsystem( enumerate, "block" );
97 ::udev_enumerate_add_match_property( enumerate, "ID_CDROM", "1" );
98 ::udev_enumerate_scan_devices( enumerate );
100 struct udev_list_entry * entry = 0;
101 udev_list_entry_foreach( entry, ::udev_enumerate_get_list_entry( enumerate ) )
103 zypp::AutoDispose<struct udev_device *> device( ::udev_device_new_from_syspath( ::udev_enumerate_get_udev( enumerate ),
104 ::udev_list_entry_get_name( entry ) ),
105 ::udev_device_unref );
108 ERR << "Can't create udev device." << endl;
112 if ( supportingDVD_r && ! ::udev_device_get_property_value( device, "ID_CDROM_DVD" ) )
114 continue; // looking for dvd only
117 const char * devnodePtr( ::udev_device_get_devnode( device ) );
120 ERR << "Got NULL devicenode." << endl;
124 // In case we need it someday:
125 //const char * mountpath = ::udev_device_get_property_value( device, "FSTAB_DIR" );
127 PathInfo devnode( devnodePtr );
128 if ( devnode.isBlk() )
130 MediaSource media( "cdrom", devnode.path().asString(), devnode.major(), devnode.minor() );
131 DBG << "Found (udev): " << media << std::endl;
132 detected.push_back( media );
135 if ( detected.empty() )
137 WAR << "Did not find any CD/DVD device." << endl;
140 using namespace zypp::target::hal;
143 HalContext hal(true);
145 std::vector<std::string> drv_udis;
146 drv_udis = hal.findDevicesByCapability("storage.cdrom");
148 DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
149 for(size_t d = 0; d < drv_udis.size(); d++)
151 HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
155 bool supportsDVD=false;
158 std::vector<std::string> caps;
160 caps = drv.getCdromCapabilityNames();
162 catch(const HalException &e)
167 std::vector<std::string>::const_iterator ci;
168 for( ci=caps.begin(); ci != caps.end(); ++ci)
175 MediaSource media("cdrom", drv.getDeviceFile(),
176 drv.getDeviceMajor(),
177 drv.getDeviceMinor());
178 DBG << "Found " << drv_udis[d] << ": "
179 << media.asString() << std::endl;
180 if( supportingDVD_r && supportsDVD)
182 detected.push_front(media);
186 detected.push_back(media);
191 catch(const zypp::target::hal::HalException &e)
200 //////////////////////////////////////////////////////////////////
203 MediaCD::MediaCD( const Url & url_r, const Pathname & attach_point_hint_r )
204 : MediaHandler( url_r, attach_point_hint_r, url_r.getPathName(), false )
206 , _lastdev_tried( -1 )
208 MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")" << endl;
210 if ( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd" )
212 ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
213 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
216 string devices = _url.getQueryParam( "devices" );
217 if ( ! devices.empty() )
219 std::vector<std::string> words;
220 str::split( devices, std::back_inserter(words), "," );
221 for ( const std::string & device : words )
223 if ( device.empty() )
226 MediaSource media( "cdrom", device, 0, 0 );
227 _devices.push_back( media );
228 DBG << "use device (delayed verify)" << device << endl;
233 DBG << "going to use on-demand device list" << endl;
237 if ( _devices.empty() )
239 ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
240 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
244 ///////////////////////////////////////////////////////////////////
247 // METHOD NAME : MediaCD::openTray
248 // METHOD TYPE : bool
250 bool MediaCD::openTray( const std::string & device_r )
252 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK|O_CLOEXEC );
257 res = ::ioctl( fd, CDROMEJECT );
265 WAR << "Unable to open '" << device_r
266 << "' (" << ::strerror( errno ) << ")" << endl;
270 WAR << "Eject " << device_r
271 << " failed (" << ::strerror( errno ) << ")" << endl;
274 #if defined(EJECT_TOOL_PATH)
275 DBG << "Try to eject " << device_r << " using "
276 << EJECT_TOOL_PATH << " utility" << std::endl;
279 cmd[0] = EJECT_TOOL_PATH;
280 cmd[1] = device_r.c_str();
282 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
284 for(std::string out( eject.receiveLine());
285 out.length(); out = eject.receiveLine())
290 if(eject.close() != 0)
292 WAR << "Eject of " << device_r << " failed." << std::endl;
299 MIL << "Eject of " << device_r << " successful." << endl;
303 ///////////////////////////////////////////////////////////////////
306 // METHOD NAME : MediaCD::closeTray
307 // METHOD TYPE : bool
309 bool MediaCD::closeTray( const std::string & device_r )
311 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK|O_CLOEXEC );
313 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
316 int res = ::ioctl( fd, CDROMCLOSETRAY );
319 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
322 DBG << "Close tray " << device_r << endl;
327 MediaCD::DeviceList MediaCD::detectDevices( bool supportingDVD_r ) const
329 DeviceList detected( systemDetectDevices( supportingDVD_r ) );
331 if ( detected.empty() )
333 WAR << "CD/DVD drive detection with HAL/UDEV failed! Guessing..." << std::endl;
334 PathInfo dvdinfo( "/dev/dvd" );
335 PathInfo cdrinfo( "/dev/cdrom" );
336 if ( dvdinfo.isBlk() )
338 MediaSource media( "cdrom", dvdinfo.path().asString(), dvdinfo.major(), dvdinfo.minor() );
339 DBG << "Found (GUESS): " << media << std::endl;
340 detected.push_back( media );
343 && ! ( cdrinfo.major() == dvdinfo.major() && cdrinfo.minor() == dvdinfo.minor() ) )
345 MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.major(), cdrinfo.minor() );
346 DBG << "Found (GUESS): " << media << std::endl;
347 detected.push_back( media );
351 // NOTE: On the fly build on-demand device list. Code was moved to
352 // here to get rid of code duplication, while keeping the ABI. Acuallty
353 // this code should be moved to a _devices accessor method.
354 if ( _devices.empty() )
356 DBG << "creating on-demand device list" << endl;
357 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
358 string device( "/dev/cdrom" );
359 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
364 PathInfo dinfo( device );
367 MediaSource media( "cdrom", device, dinfo.major(), dinfo.minor() );
368 if ( detected.empty() )
370 _devices.push_front( media ); // better try this than nothing
374 for( const auto & d : detected )
376 // /dev/cdrom or /dev/dvd to the front
377 if ( media.equals( d ) )
378 _devices.push_front( d );
380 _devices.push_back( d );
386 // no /dev/cdrom or /dev/dvd link
395 ///////////////////////////////////////////////////////////////////
398 // METHOD NAME : MediaCD::attachTo
399 // METHOD TYPE : PMError
401 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
403 void MediaCD::attachTo( bool next )
405 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
406 if ( next && _lastdev == -1 )
407 ZYPP_THROW(MediaNotSupportedException(url()));
409 // This also fills the _devices list on demand
410 DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
413 MediaMountException merr;
414 string mountpoint = attachPoint().asString();
416 string options = _url.getQueryParam( "mountoptions" );
417 if ( options.empty() )
422 //TODO: make configurable
423 list<string> filesystems;
425 // if DVD, try UDF filesystem before iso9660
426 if ( _url.getScheme() == "dvd" )
427 filesystems.push_back("udf");
429 filesystems.push_back("iso9660");
431 // try all devices in sequence
433 bool mountsucceeded = false;
434 for ( DeviceList::iterator it = _devices.begin() ; ! mountsucceeded && it != _devices.end() ; ++it, ++count )
436 DBG << "count " << count << endl;
437 if (next && count <=_lastdev_tried )
439 DBG << "skipping device " << it->name << endl;
442 _lastdev_tried = count;
444 // bnc#755815: _devices contains either devices passed as url option
445 // or autodetected ones. Accept both as long as they are block
447 MediaSource temp( *it );
448 PathInfo dinfo( temp.name );
449 if ( ! dinfo.isBlk() )
451 WAR << "skipping non block device: " << dinfo << endl;
454 DBG << "trying device " << dinfo << endl;
456 temp.maj_nr = dinfo.major();
457 temp.min_nr = dinfo.minor();
458 MediaSourceRef media( new MediaSource(temp));
459 AttachedMedia ret( findAttachedMedia( media));
461 if( ret.mediaSource && ret.attachPoint &&
462 !ret.attachPoint->empty())
464 DBG << "Using a shared media "
465 << ret.mediaSource->name
467 << ret.attachPoint->path
470 setAttachPoint(ret.attachPoint);
471 setMediaSource(ret.mediaSource);
473 mountsucceeded = true;
478 MediaManager manager;
479 MountEntries entries( manager.getMountEntries());
480 MountEntries::const_iterator e;
481 for( e = entries.begin(); e != entries.end(); ++e)
483 bool is_device = false;
484 std::string dev_path(Pathname(e->src).asString());
487 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
488 dev_info(e->src) && dev_info.isBlk())
493 if( is_device && media->maj_nr == dev_info.major() &&
494 media->min_nr == dev_info.minor())
496 AttachPointRef ap( new AttachPoint(e->dir, false));
497 AttachedMedia am( media, ap);
499 DBG << "Using a system mounted media "
505 media->iown = false; // mark attachment as foreign
507 setMediaSource(media);
510 mountsucceeded = true;
520 closeTray( it->name );
522 // try all filesystems in sequence
523 for(list<string>::iterator fsit = filesystems.begin()
524 ; !mountsucceeded && fsit != filesystems.end()
529 if( !isUseableAttachPoint(Pathname(mountpoint)))
531 mountpoint = createAttachPoint().asString();
532 setAttachPoint( mountpoint, true);
533 if( mountpoint.empty())
535 ZYPP_THROW( MediaBadAttachPointException(url()));
539 mount.mount(it->name, mountpoint, *fsit, options);
541 setMediaSource(media);
543 // wait for /etc/mtab update ...
544 // (shouldn't be needed)
546 while( !(mountsucceeded=isAttached()) && --limit)
548 WAR << "Wait for /proc/mounts update and retry...." << endl;
558 setMediaSource(MediaSourceRef());
561 mount.umount(attachPoint().asString());
563 catch (const MediaException & excpt_r)
565 ZYPP_CAUGHT(excpt_r);
567 ZYPP_THROW(MediaMountException(
568 "Unable to verify that the media was mounted",
573 catch (const MediaMountException &e)
579 catch (const MediaException & excpt_r)
582 ZYPP_CAUGHT(excpt_r);
591 if( !merr.mountOutput().empty())
593 ZYPP_THROW(MediaMountException(merr.mountError(),
596 merr.mountOutput()));
600 ZYPP_THROW(MediaMountException("Mounting media failed",
601 _url.asString(), mountpoint));
604 DBG << _lastdev << " " << count << endl;
608 ///////////////////////////////////////////////////////////////////
611 // METHOD NAME : MediaCD::releaseFrom
612 // METHOD TYPE : PMError
614 // DESCRIPTION : Asserted that media is attached.
616 void MediaCD::releaseFrom( const std::string & ejectDev )
621 AttachedMedia am( attachedMedia());
622 if(am.mediaSource && am.mediaSource->iown)
623 mount.umount(am.attachPoint->path.asString());
625 catch (const Exception & excpt_r)
627 ZYPP_CAUGHT(excpt_r);
628 if (!ejectDev.empty())
630 forceRelaseAllMedia(false);
631 if(openTray( ejectDev ))
634 ZYPP_RETHROW(excpt_r);
638 if (!ejectDev.empty())
640 forceRelaseAllMedia(false);
641 if( !openTray( ejectDev ))
643 #if REPORT_EJECT_ERRORS
644 ZYPP_THROW(MediaNotEjectedException(ejectDev));
650 ///////////////////////////////////////////////////////////////////
653 // METHOD NAME : MediaCD::forceEject
654 // METHOD TYPE : void
656 // Asserted that media is not attached.
658 void MediaCD::forceEject( const std::string & ejectDev_r )
660 bool ejected = false;
661 if ( ! isAttached() ) // no device mounted in this instance
663 // This also fills the _devices list on demand
664 DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
665 for_( it, _devices.begin(), _devices.end() )
667 MediaSourceRef media( new MediaSource( *it ) );
668 if ( media->name != ejectDev_r )
671 // bnc#755815: _devices contains either devices passed as url option
672 // or autodetected ones. Accept both as long as they are block
674 PathInfo dinfo( media->name );
675 if( ! dinfo.isBlk() )
677 WAR << "skipping non block device: " << dinfo << endl;
680 DBG << "trying device " << dinfo << endl;
682 // FIXME: we have also to check if it is mounted in the system
683 AttachedMedia ret( findAttachedMedia( media));
684 if( !ret.mediaSource )
686 forceRelaseAllMedia( media, false );
687 if ( openTray( it->name ) )
690 break; // on 1st success
695 #if REPORT_EJECT_ERRORS
698 ZYPP_THROW(MediaNotEjectedException());
703 ///////////////////////////////////////////////////////////////////
705 // METHOD NAME : MediaCD::isAttached
706 // METHOD TYPE : bool
708 // DESCRIPTION : Override check if media is attached.
711 MediaCD::isAttached() const
713 return checkAttached(false);
716 ///////////////////////////////////////////////////////////////////
718 // METHOD NAME : MediaCD::getFile
719 // METHOD TYPE : PMError
721 // DESCRIPTION : Asserted that media is attached.
723 void MediaCD::getFile( const Pathname & filename ) const
725 MediaHandler::getFile( filename );
728 ///////////////////////////////////////////////////////////////////
730 // METHOD NAME : MediaCD::getDir
731 // METHOD TYPE : PMError
733 // DESCRIPTION : Asserted that media is attached.
735 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
737 MediaHandler::getDir( dirname, recurse_r );
740 ///////////////////////////////////////////////////////////////////
743 // METHOD NAME : MediaCD::getDirInfo
744 // METHOD TYPE : PMError
746 // DESCRIPTION : Asserted that media is attached and retlist is empty.
748 void MediaCD::getDirInfo( std::list<std::string> & retlist,
749 const Pathname & dirname, bool dots ) const
751 MediaHandler::getDirInfo( retlist, dirname, dots );
754 ///////////////////////////////////////////////////////////////////
757 // METHOD NAME : MediaCD::getDirInfo
758 // METHOD TYPE : PMError
760 // DESCRIPTION : Asserted that media is attached and retlist is empty.
762 void MediaCD::getDirInfo( filesystem::DirContent & retlist, const Pathname & dirname, bool dots ) const
764 MediaHandler::getDirInfo( retlist, dirname, dots );
768 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
770 return MediaHandler::getDoesFileExist( filename );
774 bool MediaCD::hasMoreDevices()
776 if (_devices.size() == 0)
778 else if (_lastdev_tried < 0)
781 return (unsigned) _lastdev_tried < _devices.size() - 1;
785 void MediaCD::getDetectedDevices( std::vector<std::string> & devices, unsigned int & index ) const
787 if ( ! devices.empty() )
790 if ( _devices.empty() )
791 // This also fills the _devices list on demand
792 detectDevices( _url.getScheme() == "dvd" ? true : false );
794 for ( const auto & it : _devices )
795 devices.push_back( it.name );
797 index = ( _lastdev >= 0 ? (unsigned)_lastdev : 0 );
799 MIL << "got " << devices.size() << " detected devices, current: "
800 << (index < devices.size() ? devices[index] : "<none>")
801 << "(" << index << ")" << endl;
805 //////////////////////////////////////////////////////////////////
807 //////////////////////////////////////////////////////////////////