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.devMajor(), devnode.devMinor() );
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.devMajor(), dvdinfo.devMinor() );
339 DBG << "Found (GUESS): " << media << std::endl;
340 detected.push_back( media );
343 && ! ( cdrinfo.devMajor() == dvdinfo.devMajor() && cdrinfo.devMinor() == dvdinfo.devMinor() ) )
345 MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.devMajor(), cdrinfo.devMinor() );
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.devMajor(), dinfo.devMinor() );
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 ) );
412 if( !isUseableAttachPoint( attachPoint() ) )
414 setAttachPoint( createAttachPoint(), true );
416 std::string mountpoint( attachPoint().asString() );
419 MediaMountException merr;
421 string options = _url.getQueryParam( "mountoptions" );
422 if ( options.empty() )
427 //TODO: make configurable
428 list<string> filesystems;
430 // if DVD, try UDF filesystem before iso9660
431 if ( _url.getScheme() == "dvd" )
432 filesystems.push_back("udf");
434 filesystems.push_back("iso9660");
436 // try all devices in sequence
438 bool mountsucceeded = false;
439 for ( DeviceList::iterator it = _devices.begin() ; ! mountsucceeded && it != _devices.end() ; ++it, ++count )
441 DBG << "count " << count << endl;
442 if (next && count <=_lastdev_tried )
444 DBG << "skipping device " << it->name << endl;
447 _lastdev_tried = count;
449 // bnc#755815: _devices contains either devices passed as url option
450 // or autodetected ones. Accept both as long as they are block
452 MediaSource temp( *it );
453 PathInfo dinfo( temp.name );
454 if ( ! dinfo.isBlk() )
456 WAR << "skipping non block device: " << dinfo << endl;
459 DBG << "trying device " << dinfo << endl;
461 temp.maj_nr = dinfo.devMajor();
462 temp.min_nr = dinfo.devMinor();
463 MediaSourceRef media( new MediaSource(temp));
464 AttachedMedia ret( findAttachedMedia( media));
466 if( ret.mediaSource && ret.attachPoint &&
467 !ret.attachPoint->empty())
469 DBG << "Using a shared media "
470 << ret.mediaSource->name
472 << ret.attachPoint->path
475 setAttachPoint(ret.attachPoint);
476 setMediaSource(ret.mediaSource);
478 mountsucceeded = true;
483 MediaManager manager;
484 MountEntries entries( manager.getMountEntries());
485 MountEntries::const_iterator e;
486 for( e = entries.begin(); e != entries.end(); ++e)
488 bool is_device = false;
489 std::string dev_path(Pathname(e->src).asString());
492 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
493 dev_info(e->src) && dev_info.isBlk())
498 if( is_device && media->maj_nr == dev_info.devMajor() &&
499 media->min_nr == dev_info.devMinor())
501 AttachPointRef ap( new AttachPoint(e->dir, false));
502 AttachedMedia am( media, ap);
504 DBG << "Using a system mounted media "
510 media->iown = false; // mark attachment as foreign
512 setMediaSource(media);
515 mountsucceeded = true;
525 closeTray( it->name );
527 // try all filesystems in sequence
528 for(list<string>::iterator fsit = filesystems.begin()
529 ; !mountsucceeded && fsit != filesystems.end()
534 mount.mount(it->name, mountpoint, *fsit, options);
536 setMediaSource(media);
538 // wait for /etc/mtab update ...
539 // (shouldn't be needed)
541 while( !(mountsucceeded=isAttached()) && --limit)
543 WAR << "Wait for /proc/mounts update and retry...." << endl;
553 setMediaSource(MediaSourceRef());
556 mount.umount(attachPoint().asString());
558 catch (const MediaException & excpt_r)
560 ZYPP_CAUGHT(excpt_r);
562 ZYPP_THROW(MediaMountException(
563 "Unable to verify that the media was mounted",
568 catch (const MediaMountException &e)
574 catch (const MediaException & excpt_r)
577 ZYPP_CAUGHT(excpt_r);
586 if( !merr.mountOutput().empty())
588 ZYPP_THROW(MediaMountException(merr.mountError(),
591 merr.mountOutput()));
595 ZYPP_THROW(MediaMountException("Mounting media failed",
596 _url.asString(), mountpoint));
599 DBG << _lastdev << " " << count << endl;
603 ///////////////////////////////////////////////////////////////////
606 // METHOD NAME : MediaCD::releaseFrom
607 // METHOD TYPE : PMError
609 // DESCRIPTION : Asserted that media is attached.
611 void MediaCD::releaseFrom( const std::string & ejectDev )
616 AttachedMedia am( attachedMedia());
617 if(am.mediaSource && am.mediaSource->iown)
618 mount.umount(am.attachPoint->path.asString());
620 catch (const Exception & excpt_r)
622 ZYPP_CAUGHT(excpt_r);
623 if (!ejectDev.empty())
625 forceRelaseAllMedia(false);
626 if(openTray( ejectDev ))
629 ZYPP_RETHROW(excpt_r);
633 if (!ejectDev.empty())
635 forceRelaseAllMedia(false);
636 if( !openTray( ejectDev ))
638 #if REPORT_EJECT_ERRORS
639 ZYPP_THROW(MediaNotEjectedException(ejectDev));
645 ///////////////////////////////////////////////////////////////////
648 // METHOD NAME : MediaCD::forceEject
649 // METHOD TYPE : void
651 // Asserted that media is not attached.
653 void MediaCD::forceEject( const std::string & ejectDev_r )
655 #if REPORT_EJECT_ERRORS
656 bool ejected = false;
658 if ( ! isAttached() ) // no device mounted in this instance
660 // This also fills the _devices list on demand
661 DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
662 for_( it, _devices.begin(), _devices.end() )
664 MediaSourceRef media( new MediaSource( *it ) );
665 if ( media->name != ejectDev_r )
668 // bnc#755815: _devices contains either devices passed as url option
669 // or autodetected ones. Accept both as long as they are block
671 PathInfo dinfo( media->name );
672 if( ! dinfo.isBlk() )
674 WAR << "skipping non block device: " << dinfo << endl;
677 DBG << "trying device " << dinfo << endl;
679 // FIXME: we have also to check if it is mounted in the system
680 AttachedMedia ret( findAttachedMedia( media));
681 if( !ret.mediaSource )
683 forceRelaseAllMedia( media, false );
684 if ( openTray( it->name ) )
686 #if REPORT_EJECT_ERRORS
689 break; // on 1st success
694 #if REPORT_EJECT_ERRORS
697 ZYPP_THROW(MediaNotEjectedException());
702 ///////////////////////////////////////////////////////////////////
704 // METHOD NAME : MediaCD::isAttached
705 // METHOD TYPE : bool
707 // DESCRIPTION : Override check if media is attached.
710 MediaCD::isAttached() const
712 return checkAttached(false);
715 ///////////////////////////////////////////////////////////////////
717 // METHOD NAME : MediaCD::getFile
718 // METHOD TYPE : PMError
720 // DESCRIPTION : Asserted that media is attached.
722 void MediaCD::getFile( const Pathname & filename ) const
724 MediaHandler::getFile( filename );
727 ///////////////////////////////////////////////////////////////////
729 // METHOD NAME : MediaCD::getDir
730 // METHOD TYPE : PMError
732 // DESCRIPTION : Asserted that media is attached.
734 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
736 MediaHandler::getDir( dirname, recurse_r );
739 ///////////////////////////////////////////////////////////////////
742 // METHOD NAME : MediaCD::getDirInfo
743 // METHOD TYPE : PMError
745 // DESCRIPTION : Asserted that media is attached and retlist is empty.
747 void MediaCD::getDirInfo( std::list<std::string> & retlist,
748 const Pathname & dirname, bool dots ) const
750 MediaHandler::getDirInfo( retlist, dirname, dots );
753 ///////////////////////////////////////////////////////////////////
756 // METHOD NAME : MediaCD::getDirInfo
757 // METHOD TYPE : PMError
759 // DESCRIPTION : Asserted that media is attached and retlist is empty.
761 void MediaCD::getDirInfo( filesystem::DirContent & retlist, const Pathname & dirname, bool dots ) const
763 MediaHandler::getDirInfo( retlist, dirname, dots );
767 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
769 return MediaHandler::getDoesFileExist( filename );
773 bool MediaCD::hasMoreDevices()
775 if (_devices.size() == 0)
777 else if (_lastdev_tried < 0)
780 return (unsigned) _lastdev_tried < _devices.size() - 1;
784 void MediaCD::getDetectedDevices( std::vector<std::string> & devices, unsigned int & index ) const
786 if ( ! devices.empty() )
789 if ( _devices.empty() )
790 // This also fills the _devices list on demand
791 detectDevices( _url.getScheme() == "dvd" ? true : false );
793 for ( const auto & it : _devices )
794 devices.push_back( it.name );
796 index = ( _lastdev >= 0 ? (unsigned)_lastdev : 0 );
798 MIL << "got " << devices.size() << " detected devices, current: "
799 << (index < devices.size() ? devices[index] : "<none>")
800 << "(" << index << ")" << endl;
804 //////////////////////////////////////////////////////////////////
806 //////////////////////////////////////////////////////////////////