1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCD.cc
15 #include "zypp/base/Logger.h"
16 #include "zypp/ExternalProgram.h"
17 #include "zypp/media/Mount.h"
18 #include "zypp/media/MediaCD.h"
19 #include "zypp/media/MediaManager.h"
21 #include "zypp/target/hal/HalContext.h"
23 #include <cstring> // strerror
24 #include <cstdlib> // getenv
29 #include <sys/ioctl.h>
32 #include <unistd.h> // geteuid, ...
34 #include <linux/cdrom.h>
37 ** try umount of foreign (user/automounter) media on eject
38 ** 0 = don't force, 1 = automounted only, 2 == all
40 #define FORCE_RELEASE_FOREIGN 2
43 ** Reuse foreign (user/automounter) mount points.
44 ** 0 = don't use, 1 = automounted only, 2 = all
46 #define REUSE_FOREIGN_MOUNTS 2
49 ** if to throw exception on eject errors or ignore them
51 #define REPORT_EJECT_ERRORS 1
54 ** If defined to the full path of the eject utility,
55 ** it will be used additionally to the eject-ioctl.
57 #define EJECT_TOOL_PATH "/bin/eject"
65 ///////////////////////////////////////////////////////////////////
67 // CLASS NAME : MediaCD
69 ///////////////////////////////////////////////////////////////////
72 ///////////////////////////////////////////////////////////////////
75 // METHOD NAME : MediaCD::MediaCD
76 // METHOD TYPE : Constructor
80 MediaCD::MediaCD( const Url & url_r,
81 const Pathname & attach_point_hint_r )
82 : MediaHandler( url_r, attach_point_hint_r,
83 url_r.getPathName(), // urlpath below attachpoint
85 _lastdev(-1), _lastdev_tried(-1)
87 MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")"
90 if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
92 ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
93 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
96 string devices = _url.getQueryParam("devices");
99 string::size_type pos;
100 DBG << "parse " << devices << endl;
101 while(!devices.empty())
103 pos = devices.find(',');
104 string device = devices.substr(0,pos);
107 MediaSource media("cdrom", device, 0, 0);
108 _devices.push_back( media);
109 DBG << "use device (delayed verify)" << device << endl;
111 if (pos!=string::npos)
112 devices=devices.substr(pos+1);
119 DBG << "going to use on-demand device list" << endl;
123 if( _devices.empty())
125 ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
126 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
130 ///////////////////////////////////////////////////////////////////
133 // METHOD NAME : MediaCD::openTray
134 // METHOD TYPE : bool
136 bool MediaCD::openTray( const std::string & device_r )
138 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
143 res = ::ioctl( fd, CDROMEJECT );
151 WAR << "Unable to open '" << device_r
152 << "' (" << ::strerror( errno ) << ")" << endl;
156 WAR << "Eject " << device_r
157 << " failed (" << ::strerror( errno ) << ")" << endl;
160 #if defined(EJECT_TOOL_PATH)
161 DBG << "Try to eject " << device_r << " using "
162 << EJECT_TOOL_PATH << " utility" << std::endl;
165 cmd[0] = EJECT_TOOL_PATH;
166 cmd[1] = device_r.c_str();
168 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
170 for(std::string out( eject.receiveLine());
171 out.length(); out = eject.receiveLine())
176 if(eject.close() != 0)
178 WAR << "Eject of " << device_r << " failed." << std::endl;
185 MIL << "Eject of " << device_r << " successful." << endl;
189 ///////////////////////////////////////////////////////////////////
192 // METHOD NAME : MediaCD::closeTray
193 // METHOD TYPE : bool
195 bool MediaCD::closeTray( const std::string & device_r )
197 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
199 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
202 int res = ::ioctl( fd, CDROMCLOSETRAY );
205 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
208 DBG << "Close tray " << device_r << endl;
212 ///////////////////////////////////////////////////////////////////
215 // METHOD NAME : MediaCD::detectDevices
216 // METHOD TYPE : MediaCD::DeviceList
219 MediaCD::detectDevices(bool supportingDVD) const
224 using namespace zypp::target::hal;
227 HalContext hal(true);
229 std::vector<std::string> drv_udis;
230 drv_udis = hal.findDevicesByCapability("storage.cdrom");
232 DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
233 for(size_t d = 0; d < drv_udis.size(); d++)
235 HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
239 bool supportsDVD=false;
242 std::vector<std::string> caps;
244 caps = drv.getCdromCapabilityNames();
246 catch(const HalException &e)
251 std::vector<std::string>::const_iterator ci;
252 for( ci=caps.begin(); ci != caps.end(); ++ci)
259 MediaSource media("cdrom", drv.getDeviceFile(),
260 drv.getDeviceMajor(),
261 drv.getDeviceMinor());
262 DBG << "Found " << drv_udis[d] << ": "
263 << media.asString() << std::endl;
264 if( supportingDVD && supportsDVD)
266 detected.push_front(media);
270 detected.push_back(media);
275 catch(const zypp::target::hal::HalException &e)
280 #warning Poor CDROM devices detection without HAL
281 WAR << "Cdrom drive detection without HAL! " << std::endl;
282 PathInfo dvdinfo( "/dev/dvd" );
283 PathInfo cdrinfo( "/dev/cdrom" );
284 if ( dvdinfo.isBlk() )
286 MediaSource media( "cdrom", dvdinfo.path().asString(), dvdinfo.major(), dvdinfo.minor() );
287 DBG << "Found (NO_HAL): " << media << std::endl;
288 detected.push_back( media );
291 && ! ( cdrinfo.major() == dvdinfo.major() && cdrinfo.minor() == dvdinfo.minor() ) )
293 MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.major(), cdrinfo.minor() );
294 DBG << "Found (NO_HAL): " << media << std::endl;
295 detected.push_back( media );
302 ///////////////////////////////////////////////////////////////////
305 // METHOD NAME : MediaCD::attachTo
306 // METHOD TYPE : PMError
308 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
310 void MediaCD::attachTo(bool next)
312 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
313 if (next && _lastdev == -1)
314 ZYPP_THROW(MediaNotSupportedException(url()));
317 detectDevices(_url.getScheme() == "dvd" ? true : false));
321 DBG << "creating on-demand device list" << endl;
322 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
323 string device( "/dev/cdrom" );
324 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
329 PathInfo dinfo(device);
332 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
334 DeviceList::const_iterator d( detected.begin());
335 for( ; d != detected.end(); ++d)
337 // /dev/cdrom or /dev/dvd to the front
338 if( media.equals( *d))
339 _devices.push_front( *d);
341 _devices.push_back( *d);
346 // no /dev/cdrom or /dev/dvd link
352 string mountpoint = attachPoint().asString();
353 bool mountsucceeded = false;
355 MediaMountException merr;
357 string options = _url.getQueryParam("mountoptions");
363 //TODO: make configurable
364 list<string> filesystems;
366 // if DVD, try UDF filesystem before iso9660
367 if ( _url.getScheme() == "dvd" )
368 filesystems.push_back("udf");
370 filesystems.push_back("iso9660");
372 // try all devices in sequence
373 for (DeviceList::iterator it = _devices.begin()
374 ; !mountsucceeded && it != _devices.end()
377 DBG << "count " << count << endl;
378 if (next && count <=_lastdev_tried )
380 DBG << "skipping device " << it->name << endl;
384 _lastdev_tried = count;
386 MediaSource temp( *it);
388 PathInfo dinfo(temp.name);
391 temp.maj_nr = dinfo.major();
392 temp.min_nr = dinfo.minor();
394 DeviceList::const_iterator d( detected.begin());
395 for( ; d != detected.end(); ++d)
397 if( temp.equals( *d))
406 DBG << "skipping invalid device: " << it->name << endl;
410 MediaSourceRef media( new MediaSource(temp));
411 AttachedMedia ret( findAttachedMedia( media));
413 if( ret.mediaSource && ret.attachPoint &&
414 !ret.attachPoint->empty())
416 DBG << "Using a shared media "
417 << ret.mediaSource->name
419 << ret.attachPoint->path
422 setAttachPoint(ret.attachPoint);
423 setMediaSource(ret.mediaSource);
425 mountsucceeded = true;
429 #if REUSE_FOREIGN_MOUNTS > 0
431 MediaManager manager;
432 MountEntries entries( manager.getMountEntries());
433 MountEntries::const_iterator e;
434 for( e = entries.begin(); e != entries.end(); ++e)
436 bool is_device = false;
437 std::string dev_path(Pathname(e->src).asString());
440 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
441 dev_info(e->src) && dev_info.isBlk())
446 if( is_device && media->maj_nr == dev_info.major() &&
447 media->min_nr == dev_info.minor())
449 AttachPointRef ap( new AttachPoint(e->dir, false));
450 AttachedMedia am( media, ap);
452 // 1 = automounted only, 2 == all
454 #if REUSE_FOREIGN_MOUNTS == 1
455 if( isAutoMountedMedia(am))
458 DBG << "Using a system mounted media "
464 media->iown = false; // mark attachment as foreign
466 setMediaSource(media);
469 mountsucceeded = true;
477 #endif // REUSE_FOREIGN_MOUNTS
480 closeTray( it->name );
482 // try all filesystems in sequence
483 for(list<string>::iterator fsit = filesystems.begin()
484 ; !mountsucceeded && fsit != filesystems.end()
489 if( !isUseableAttachPoint(Pathname(mountpoint)))
491 mountpoint = createAttachPoint().asString();
492 setAttachPoint( mountpoint, true);
493 if( mountpoint.empty())
495 ZYPP_THROW( MediaBadAttachPointException(url()));
499 mount.mount(it->name, mountpoint, *fsit, options);
501 setMediaSource(media);
503 // wait for /etc/mtab update ...
504 // (shouldn't be needed)
506 while( !(mountsucceeded=isAttached()) && --limit)
517 setMediaSource(MediaSourceRef());
520 mount.umount(attachPoint().asString());
522 catch (const MediaException & excpt_r)
524 ZYPP_CAUGHT(excpt_r);
526 ZYPP_THROW(MediaMountException(
527 "Unable to verify that the media was mounted",
532 catch (const MediaMountException &e)
538 catch (const MediaException & excpt_r)
541 ZYPP_CAUGHT(excpt_r);
550 if( !merr.mountOutput().empty())
552 ZYPP_THROW(MediaMountException(merr.mountError(),
555 merr.mountOutput()));
559 ZYPP_THROW(MediaMountException("Mounting media failed",
560 _url.asString(), mountpoint));
563 DBG << _lastdev << " " << count << endl;
567 ///////////////////////////////////////////////////////////////////
570 // METHOD NAME : MediaCD::releaseFrom
571 // METHOD TYPE : PMError
573 // DESCRIPTION : Asserted that media is attached.
575 void MediaCD::releaseFrom( const std::string & ejectDev )
580 AttachedMedia am( attachedMedia());
581 if(am.mediaSource && am.mediaSource->iown)
582 mount.umount(am.attachPoint->path.asString());
584 catch (const Exception & excpt_r)
586 ZYPP_CAUGHT(excpt_r);
587 if (!ejectDev.empty())
589 #if FORCE_RELEASE_FOREIGN > 0
590 /* 1 = automounted only, 2 = all */
591 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
593 if(openTray( ejectDev ))
596 ZYPP_RETHROW(excpt_r);
600 if (!ejectDev.empty())
602 #if FORCE_RELEASE_FOREIGN > 0
603 /* 1 = automounted only, 2 = all */
604 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
606 if( !openTray( ejectDev ))
608 #if REPORT_EJECT_ERRORS
609 ZYPP_THROW(MediaNotEjectedException(ejectDev));
615 ///////////////////////////////////////////////////////////////////
618 // METHOD NAME : MediaCD::forceEject
619 // METHOD TYPE : void
621 // Asserted that media is not attached.
623 void MediaCD::forceEject(const std::string & ejectDev)
626 if ( !isAttached()) { // no device mounted in this instance
629 detectDevices(_url.getScheme() == "dvd" ? true : false));
633 DBG << "creating on-demand device list" << endl;
634 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
635 string device( "/dev/cdrom" );
636 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
640 PathInfo dinfo(device);
643 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
645 DeviceList::const_iterator d( detected.begin());
646 for( ; d != detected.end(); ++d)
648 // /dev/cdrom or /dev/dvd to the front
649 if( media.equals( *d))
650 _devices.push_front( *d);
652 _devices.push_back( *d);
657 // no /dev/cdrom or /dev/dvd link
662 DeviceList::iterator it;
663 for( it = _devices.begin(); it != _devices.end(); ++it ) {
664 MediaSourceRef media( new MediaSource( *it));
665 if (media->name != ejectDev)
669 PathInfo dinfo(media->name);
672 media->maj_nr = dinfo.major();
673 media->min_nr = dinfo.minor();
675 DeviceList::const_iterator d( detected.begin());
676 for( ; d != detected.end(); ++d)
678 if( media->equals( *d))
687 DBG << "skipping invalid device: " << it->name << endl;
691 // FIXME: we have also to check if it is mounted in the system
692 AttachedMedia ret( findAttachedMedia( media));
693 if( !ret.mediaSource)
695 #if FORCE_RELEASE_FOREIGN > 0
696 /* 1 = automounted only, 2 = all */
697 forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
699 if ( openTray( it->name ) )
702 break; // on 1st success
709 #if REPORT_EJECT_ERRORS
710 ZYPP_THROW(MediaNotEjectedException());
715 bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
717 bool is_automounted = false;
718 if( media.mediaSource && !media.mediaSource->name.empty())
721 using namespace zypp::target::hal;
724 HalContext hal(true);
726 HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
729 std::string udi = vol.getUDI();
735 key = "info.hal_mount.created_mount_point";
736 mnt = hal.getDevicePropertyString(udi, key);
738 if(media.attachPoint->path == mnt)
739 is_automounted = true;
741 catch(const HalException &e1)
747 key = "volume.mount_point";
748 mnt = hal.getDevicePropertyString(udi, key);
750 if(media.attachPoint->path == mnt)
751 is_automounted = true;
753 catch(const HalException &e2)
760 catch(const HalException &e)
765 #warning Can not detect automounted media without HAL
766 INT << "Can not detect automounted media without HAL!" << endl;
767 // ma@: This codepath is probably unused due to 'REUSE_FOREIGN_MOUNTS == 2'
768 // Maybe we should cleanup all this automount-specail-handling.
771 DBG << "Media " << media.mediaSource->asString()
772 << " attached on " << media.attachPoint->path
773 << " is" << (is_automounted ? "" : " not")
774 << " automounted" << std::endl;
775 return is_automounted;
778 ///////////////////////////////////////////////////////////////////
780 // METHOD NAME : MediaCD::isAttached
781 // METHOD TYPE : bool
783 // DESCRIPTION : Override check if media is attached.
786 MediaCD::isAttached() const
788 return checkAttached(false);
791 ///////////////////////////////////////////////////////////////////
793 // METHOD NAME : MediaCD::getFile
794 // METHOD TYPE : PMError
796 // DESCRIPTION : Asserted that media is attached.
798 void MediaCD::getFile( const Pathname & filename ) const
800 MediaHandler::getFile( filename );
803 ///////////////////////////////////////////////////////////////////
805 // METHOD NAME : MediaCD::getDir
806 // METHOD TYPE : PMError
808 // DESCRIPTION : Asserted that media is attached.
810 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
812 MediaHandler::getDir( dirname, recurse_r );
815 ///////////////////////////////////////////////////////////////////
818 // METHOD NAME : MediaCD::getDirInfo
819 // METHOD TYPE : PMError
821 // DESCRIPTION : Asserted that media is attached and retlist is empty.
823 void MediaCD::getDirInfo( std::list<std::string> & retlist,
824 const Pathname & dirname, bool dots ) const
826 MediaHandler::getDirInfo( retlist, dirname, dots );
829 ///////////////////////////////////////////////////////////////////
832 // METHOD NAME : MediaCD::getDirInfo
833 // METHOD TYPE : PMError
835 // DESCRIPTION : Asserted that media is attached and retlist is empty.
837 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
838 const Pathname & dirname, bool dots ) const
840 MediaHandler::getDirInfo( retlist, dirname, dots );
843 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
845 return MediaHandler::getDoesFileExist( filename );
848 bool MediaCD::hasMoreDevices()
850 if (_devices.size() == 0)
852 else if (_lastdev_tried < 0)
855 return (unsigned) _lastdev_tried < _devices.size() - 1;
859 MediaCD::getDetectedDevices(std::vector<std::string> & devices,
860 unsigned int & index) const
863 if (!devices.empty())
866 for (DeviceList::const_iterator it = _devices.begin();
867 it != _devices.end(); ++it)
868 devices.push_back(it->name);
873 // try to detect again if _devices are empty (maybe this method will be
874 // called before _devices get actually filled)
877 DBG << "no device list so far, trying to detect" << endl;
880 detectDevices(_url.getScheme() == "dvd" ? true : false));
882 for (DeviceList::const_iterator it = detected.begin();
883 it != detected.end(); ++it)
884 devices.push_back(it->name);
886 // don't know which one is in use in this case
890 MIL << "got " << devices.size() << " detected devices, current: "
891 << (index < devices.size() ? devices[index] : "<none>")
892 << "(" << index << ")" << endl;
898 // vim: set ts=8 sts=2 sw=2 ai noet: