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 ** verify devices names as late as possible (while attach)
39 #define DELAYED_VERIFY 1
42 ** try umount of foreign (user/automounter) media on eject
43 ** 0 = don't force, 1 = automounted only, 2 == all
45 #define FORCE_RELEASE_FOREIGN 2
48 ** Reuse foreign (user/automounter) mount points.
49 ** 0 = don't use, 1 = automounted only, 2 = all
51 #define REUSE_FOREIGN_MOUNTS 2
54 ** if to throw exception on eject errors or ignore them
56 #define REPORT_EJECT_ERRORS 1
59 ** If defined to the full path of the eject utility,
60 ** it will be used additionally to the eject-ioctl.
62 #define EJECT_TOOL_PATH "/bin/eject"
70 ///////////////////////////////////////////////////////////////////
72 // CLASS NAME : MediaCD
74 ///////////////////////////////////////////////////////////////////
76 ///////////////////////////////////////////////////////////////////
79 // METHOD NAME : MediaCD::MediaCD
80 // METHOD TYPE : Constructor
84 MediaCD::MediaCD( const Url & url_r,
85 const Pathname & attach_point_hint_r )
86 : MediaHandler( url_r, attach_point_hint_r,
87 url_r.getPathName(), // urlpath below attachpoint
90 , _lastdev(-1), _lastdev_tried(-1)
92 MIL << "MediaCD::MediaCD(" << url_r << ", "
93 << attach_point_hint_r << ")" << endl;
95 if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
97 ERR << "Unsupported schema in the Url: " << url_r.asString()
99 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
103 DeviceList detected( detectDevices(
104 url_r.getScheme() == "dvd" ? true : false
108 string devices = _url.getQueryParam("devices");
109 if (!devices.empty())
111 string::size_type pos;
112 DBG << "parse " << devices << endl;
113 while(!devices.empty())
115 pos = devices.find(',');
116 string device = devices.substr(0,pos);
120 MediaSource media("cdrom", device, 0, 0);
121 _devices.push_back( media);
122 DBG << "use device (delayed verify)" << device << endl;
125 PathInfo dinfo(device);
128 MediaSource media("cdrom", device, dinfo.major(),
130 DeviceList::const_iterator d( detected.begin());
131 for( ; d != detected.end(); ++d)
133 if( media.equals( *d))
136 _devices.push_back( *d);
137 DBG << "use device " << device << endl;
144 ERR << "Device " << device << " is not acceptable "
145 << "for " << _url.getScheme() << std::endl;
146 ZYPP_THROW(MediaBadUrlException(_url,
147 "Invalid device name in URL devices argument"
152 if (pos!=string::npos)
153 devices=devices.substr(pos+1);
161 DBG << "going to use on-demand device list" << endl;
164 DBG << "going to use default device list" << endl;
165 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
166 string device( "/dev/cdrom" );
167 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
171 PathInfo dinfo(device);
174 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
176 DeviceList::const_iterator d( detected.begin());
177 for( ; d != detected.end(); ++d)
179 // /dev/cdrom or /dev/dvd to the front
180 if( media.equals( *d))
181 _devices.push_front( *d);
183 _devices.push_back( *d);
188 // no /dev/cdrom or /dev/dvd link
194 if( _devices.empty())
196 ERR << "Unable to find any cdrom drive for " << _url.asString()
198 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
202 ///////////////////////////////////////////////////////////////////
205 // METHOD NAME : MediaCD::openTray
206 // METHOD TYPE : bool
208 bool MediaCD::openTray( const std::string & device_r )
210 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
215 res = ::ioctl( fd, CDROMEJECT );
223 WAR << "Unable to open '" << device_r
224 << "' (" << ::strerror( errno ) << ")" << endl;
228 WAR << "Eject " << device_r
229 << " failed (" << ::strerror( errno ) << ")" << endl;
232 #if defined(EJECT_TOOL_PATH)
233 DBG << "Try to eject " << device_r << " using "
234 << EJECT_TOOL_PATH << " utility" << std::endl;
237 cmd[0] = EJECT_TOOL_PATH;
238 cmd[1] = device_r.c_str();
240 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
242 for(std::string out( eject.receiveLine());
243 out.length(); out = eject.receiveLine())
248 if(eject.close() != 0)
250 WAR << "Eject of " << device_r << " failed." << std::endl;
257 MIL << "Eject of " << device_r << " successful." << endl;
261 ///////////////////////////////////////////////////////////////////
264 // METHOD NAME : MediaCD::closeTray
265 // METHOD TYPE : bool
267 bool MediaCD::closeTray( const std::string & device_r )
269 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
271 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
274 int res = ::ioctl( fd, CDROMCLOSETRAY );
277 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
280 DBG << "Close tray " << device_r << endl;
284 ///////////////////////////////////////////////////////////////////
287 // METHOD NAME : MediaCD::detectDevices
288 // METHOD TYPE : MediaCD::DeviceList
291 MediaCD::detectDevices(bool supportingDVD)
293 using namespace zypp::target::hal;
298 HalContext hal(true);
300 std::vector<std::string> drv_udis;
301 drv_udis = hal.findDevicesByCapability("storage.cdrom");
303 DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
304 for(size_t d = 0; d < drv_udis.size(); d++)
306 HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
310 bool supportsDVD=false;
313 std::vector<std::string> caps;
315 caps = drv.getCdromCapabilityNames();
317 catch(const HalException &e)
322 std::vector<std::string>::const_iterator ci;
323 for( ci=caps.begin(); ci != caps.end(); ++ci)
330 MediaSource media("cdrom", drv.getDeviceFile(),
331 drv.getDeviceMajor(),
332 drv.getDeviceMinor());
333 DBG << "Found " << drv_udis[d] << ": "
334 << media.asString() << std::endl;
335 if( supportingDVD && supportsDVD)
337 detected.push_front(media);
341 detected.push_back(media);
346 catch(const zypp::target::hal::HalException &e)
355 ///////////////////////////////////////////////////////////////////
358 // METHOD NAME : MediaCD::attachTo
359 // METHOD TYPE : PMError
361 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
363 void MediaCD::attachTo(bool next)
365 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
366 if (next && _lastdev == -1)
367 ZYPP_THROW(MediaNotSupportedException(url()));
370 DeviceList detected( detectDevices(
371 _url.getScheme() == "dvd" ? true : false
376 DBG << "creating on-demand device list" << endl;
377 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
378 string device( "/dev/cdrom" );
379 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
383 PathInfo dinfo(device);
386 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
388 DeviceList::const_iterator d( detected.begin());
389 for( ; d != detected.end(); ++d)
391 // /dev/cdrom or /dev/dvd to the front
392 if( media.equals( *d))
393 _devices.push_front( *d);
395 _devices.push_back( *d);
400 // no /dev/cdrom or /dev/dvd link
407 string mountpoint = attachPoint().asString();
408 bool mountsucceeded = false;
410 MediaMountException merr;
412 string options = _url.getQueryParam("mountoptions");
418 //TODO: make configurable
419 list<string> filesystems;
421 // if DVD, try UDF filesystem before iso9660
422 if ( _url.getScheme() == "dvd" )
423 filesystems.push_back("udf");
425 filesystems.push_back("iso9660");
427 // try all devices in sequence
428 for (DeviceList::iterator it = _devices.begin()
429 ; !mountsucceeded && it != _devices.end()
432 DBG << "count " << count << endl;
433 if (next && count <=_lastdev_tried )
435 DBG << "skipping device " << it->name << endl;
439 _lastdev_tried = count;
442 MediaSource temp( *it);
444 PathInfo dinfo(temp.name);
447 temp.maj_nr = dinfo.major();
448 temp.min_nr = dinfo.minor();
450 DeviceList::const_iterator d( detected.begin());
451 for( ; d != detected.end(); ++d)
453 if( temp.equals( *d))
462 DBG << "skipping invalid device: " << it->name << endl;
465 MediaSourceRef media( new MediaSource(temp));
467 MediaSourceRef media( new MediaSource( *it));
470 AttachedMedia ret( findAttachedMedia( media));
472 if( ret.mediaSource && ret.attachPoint &&
473 !ret.attachPoint->empty())
475 DBG << "Using a shared media "
476 << ret.mediaSource->name
478 << ret.attachPoint->path
481 setAttachPoint(ret.attachPoint);
482 setMediaSource(ret.mediaSource);
484 mountsucceeded = true;
488 #if REUSE_FOREIGN_MOUNTS > 0
490 MediaManager manager;
491 MountEntries entries( manager.getMountEntries());
492 MountEntries::const_iterator e;
493 for( e = entries.begin(); e != entries.end(); ++e)
495 bool is_device = false;
496 std::string dev_path(Pathname(e->src).asString());
499 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
500 dev_info(e->src) && dev_info.isBlk())
505 if( is_device && media->maj_nr == dev_info.major() &&
506 media->min_nr == dev_info.minor())
508 AttachPointRef ap( new AttachPoint(e->dir, false));
509 AttachedMedia am( media, ap);
511 // 1 = automounted only, 2 == all
513 #if REUSE_FOREIGN_MOUNTS == 1
514 if( isAutoMountedMedia(am))
517 DBG << "Using a system mounted media "
523 media->iown = false; // mark attachment as foreign
525 setMediaSource(media);
528 mountsucceeded = true;
536 #endif // REUSE_FOREIGN_MOUNTS
539 closeTray( it->name );
541 // try all filesystems in sequence
542 for(list<string>::iterator fsit = filesystems.begin()
543 ; !mountsucceeded && fsit != filesystems.end()
547 if( !isUseableAttachPoint(Pathname(mountpoint)))
549 mountpoint = createAttachPoint().asString();
550 setAttachPoint( mountpoint, true);
551 if( mountpoint.empty())
553 ZYPP_THROW( MediaBadAttachPointException(url()));
557 mount.mount(it->name, mountpoint, *fsit, options);
559 setMediaSource(media);
561 // wait for /etc/mtab update ...
562 // (shouldn't be needed)
564 while( !(mountsucceeded=isAttached()) && --limit)
575 setMediaSource(MediaSourceRef());
578 mount.umount(attachPoint().asString());
580 catch (const MediaException & excpt_r)
582 ZYPP_CAUGHT(excpt_r);
584 ZYPP_THROW(MediaMountException(
585 "Unable to verify that the media was mounted",
590 catch (const MediaMountException &e)
596 catch (const MediaException & excpt_r)
599 ZYPP_CAUGHT(excpt_r);
608 if( !merr.mountOutput().empty())
610 ZYPP_THROW(MediaMountException(merr.mountError(),
613 merr.mountOutput()));
617 ZYPP_THROW(MediaMountException("Mounting media failed",
618 _url.asString(), mountpoint));
621 DBG << _lastdev << " " << count << endl;
625 ///////////////////////////////////////////////////////////////////
628 // METHOD NAME : MediaCD::releaseFrom
629 // METHOD TYPE : PMError
631 // DESCRIPTION : Asserted that media is attached.
633 void MediaCD::releaseFrom( bool eject )
637 AttachedMedia am( attachedMedia());
638 if(am.mediaSource && am.mediaSource->iown)
639 mount.umount(am.attachPoint->path.asString());
641 catch (const Exception & excpt_r)
643 ZYPP_CAUGHT(excpt_r);
646 #if FORCE_RELEASE_FOREIGN > 0
647 /* 1 = automounted only, 2 = all */
648 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
650 if(openTray( mediaSourceName()))
653 ZYPP_RETHROW(excpt_r);
659 #if FORCE_RELEASE_FOREIGN > 0
660 /* 1 = automounted only, 2 = all */
661 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
663 if( !openTray( mediaSourceName() ))
665 #if REPORT_EJECT_ERRORS
666 ZYPP_THROW(MediaNotEjectedException(mediaSourceName()));
672 ///////////////////////////////////////////////////////////////////
675 // METHOD NAME : MediaCD::forceEject
676 // METHOD TYPE : void
678 // Asserted that media is not attached.
680 void MediaCD::forceEject()
683 if ( !isAttached()) { // no device mounted in this instance
685 DeviceList detected( detectDevices(
686 _url.getScheme() == "dvd" ? true : false
691 DBG << "creating on-demand device list" << endl;
692 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
693 string device( "/dev/cdrom" );
694 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
698 PathInfo dinfo(device);
701 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
703 DeviceList::const_iterator d( detected.begin());
704 for( ; d != detected.end(); ++d)
706 // /dev/cdrom or /dev/dvd to the front
707 if( media.equals( *d))
708 _devices.push_front( *d);
710 _devices.push_back( *d);
715 // no /dev/cdrom or /dev/dvd link
721 DeviceList::iterator it;
722 for( it = _devices.begin(); it != _devices.end(); ++it ) {
723 MediaSourceRef media( new MediaSource( *it));
726 PathInfo dinfo(media->name);
729 media->maj_nr = dinfo.major();
730 media->min_nr = dinfo.minor();
732 DeviceList::const_iterator d( detected.begin());
733 for( ; d != detected.end(); ++d)
735 if( media->equals( *d))
744 DBG << "skipping invalid device: " << it->name << endl;
749 // FIXME: we have also to check if it is mounted in the system
750 AttachedMedia ret( findAttachedMedia( media));
751 if( !ret.mediaSource)
753 #if FORCE_RELEASE_FOREIGN > 0
754 /* 1 = automounted only, 2 = all */
755 forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
757 if ( openTray( it->name ) )
760 break; // on 1st success
767 #if REPORT_EJECT_ERRORS
768 ZYPP_THROW(MediaNotEjectedException());
773 bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
775 bool is_automounted = false;
776 if( media.mediaSource && !media.mediaSource->name.empty())
778 using namespace zypp::target::hal;
782 HalContext hal(true);
784 HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
787 std::string udi = vol.getUDI();
793 key = "info.hal_mount.created_mount_point";
794 mnt = hal.getDevicePropertyString(udi, key);
796 if(media.attachPoint->path == mnt)
797 is_automounted = true;
799 catch(const HalException &e1)
805 key = "volume.mount_point";
806 mnt = hal.getDevicePropertyString(udi, key);
808 if(media.attachPoint->path == mnt)
809 is_automounted = true;
811 catch(const HalException &e2)
818 catch(const HalException &e)
823 DBG << "Media " << media.mediaSource->asString()
824 << " attached on " << media.attachPoint->path
825 << " is" << (is_automounted ? "" : " not")
826 << " automounted" << std::endl;
827 return is_automounted;
830 ///////////////////////////////////////////////////////////////////
832 // METHOD NAME : MediaCD::isAttached
833 // METHOD TYPE : bool
835 // DESCRIPTION : Override check if media is attached.
838 MediaCD::isAttached() const
840 return checkAttached(false);
843 ///////////////////////////////////////////////////////////////////
845 // METHOD NAME : MediaCD::getFile
846 // METHOD TYPE : PMError
848 // DESCRIPTION : Asserted that media is attached.
850 void MediaCD::getFile( const Pathname & filename ) const
852 MediaHandler::getFile( filename );
855 ///////////////////////////////////////////////////////////////////
857 // METHOD NAME : MediaCD::getDir
858 // METHOD TYPE : PMError
860 // DESCRIPTION : Asserted that media is attached.
862 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
864 MediaHandler::getDir( dirname, recurse_r );
867 ///////////////////////////////////////////////////////////////////
870 // METHOD NAME : MediaCD::getDirInfo
871 // METHOD TYPE : PMError
873 // DESCRIPTION : Asserted that media is attached and retlist is empty.
875 void MediaCD::getDirInfo( std::list<std::string> & retlist,
876 const Pathname & dirname, bool dots ) const
878 MediaHandler::getDirInfo( retlist, dirname, dots );
881 ///////////////////////////////////////////////////////////////////
884 // METHOD NAME : MediaCD::getDirInfo
885 // METHOD TYPE : PMError
887 // DESCRIPTION : Asserted that media is attached and retlist is empty.
889 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
890 const Pathname & dirname, bool dots ) const
892 MediaHandler::getDirInfo( retlist, dirname, dots );
895 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
897 return MediaHandler::getDoesFileExist( filename );
900 bool MediaCD::hasMoreDevices()
902 if (_devices.size() == 0)
904 else if (_lastdev_tried < 0)
907 return (unsigned) _lastdev_tried < _devices.size() - 1;
911 // vim: set ts=8 sts=2 sw=2 ai noet: