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
221 using namespace zypp::target::hal;
226 HalContext hal(true);
228 std::vector<std::string> drv_udis;
229 drv_udis = hal.findDevicesByCapability("storage.cdrom");
231 DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
232 for(size_t d = 0; d < drv_udis.size(); d++)
234 HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
238 bool supportsDVD=false;
241 std::vector<std::string> caps;
243 caps = drv.getCdromCapabilityNames();
245 catch(const HalException &e)
250 std::vector<std::string>::const_iterator ci;
251 for( ci=caps.begin(); ci != caps.end(); ++ci)
258 MediaSource media("cdrom", drv.getDeviceFile(),
259 drv.getDeviceMajor(),
260 drv.getDeviceMinor());
261 DBG << "Found " << drv_udis[d] << ": "
262 << media.asString() << std::endl;
263 if( supportingDVD && supportsDVD)
265 detected.push_front(media);
269 detected.push_back(media);
274 catch(const zypp::target::hal::HalException &e)
283 ///////////////////////////////////////////////////////////////////
286 // METHOD NAME : MediaCD::attachTo
287 // METHOD TYPE : PMError
289 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
291 void MediaCD::attachTo(bool next)
293 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
294 if (next && _lastdev == -1)
295 ZYPP_THROW(MediaNotSupportedException(url()));
298 detectDevices(_url.getScheme() == "dvd" ? true : false));
302 DBG << "creating on-demand device list" << endl;
303 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
304 string device( "/dev/cdrom" );
305 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
310 PathInfo dinfo(device);
313 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
315 DeviceList::const_iterator d( detected.begin());
316 for( ; d != detected.end(); ++d)
318 // /dev/cdrom or /dev/dvd to the front
319 if( media.equals( *d))
320 _devices.push_front( *d);
322 _devices.push_back( *d);
327 // no /dev/cdrom or /dev/dvd link
333 string mountpoint = attachPoint().asString();
334 bool mountsucceeded = false;
336 MediaMountException merr;
338 string options = _url.getQueryParam("mountoptions");
344 //TODO: make configurable
345 list<string> filesystems;
347 // if DVD, try UDF filesystem before iso9660
348 if ( _url.getScheme() == "dvd" )
349 filesystems.push_back("udf");
351 filesystems.push_back("iso9660");
353 // try all devices in sequence
354 for (DeviceList::iterator it = _devices.begin()
355 ; !mountsucceeded && it != _devices.end()
358 DBG << "count " << count << endl;
359 if (next && count <=_lastdev_tried )
361 DBG << "skipping device " << it->name << endl;
365 _lastdev_tried = count;
367 MediaSource temp( *it);
369 PathInfo dinfo(temp.name);
372 temp.maj_nr = dinfo.major();
373 temp.min_nr = dinfo.minor();
375 DeviceList::const_iterator d( detected.begin());
376 for( ; d != detected.end(); ++d)
378 if( temp.equals( *d))
387 DBG << "skipping invalid device: " << it->name << endl;
391 MediaSourceRef media( new MediaSource(temp));
392 AttachedMedia ret( findAttachedMedia( media));
394 if( ret.mediaSource && ret.attachPoint &&
395 !ret.attachPoint->empty())
397 DBG << "Using a shared media "
398 << ret.mediaSource->name
400 << ret.attachPoint->path
403 setAttachPoint(ret.attachPoint);
404 setMediaSource(ret.mediaSource);
406 mountsucceeded = true;
410 #if REUSE_FOREIGN_MOUNTS > 0
412 MediaManager manager;
413 MountEntries entries( manager.getMountEntries());
414 MountEntries::const_iterator e;
415 for( e = entries.begin(); e != entries.end(); ++e)
417 bool is_device = false;
418 std::string dev_path(Pathname(e->src).asString());
421 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
422 dev_info(e->src) && dev_info.isBlk())
427 if( is_device && media->maj_nr == dev_info.major() &&
428 media->min_nr == dev_info.minor())
430 AttachPointRef ap( new AttachPoint(e->dir, false));
431 AttachedMedia am( media, ap);
433 // 1 = automounted only, 2 == all
435 #if REUSE_FOREIGN_MOUNTS == 1
436 if( isAutoMountedMedia(am))
439 DBG << "Using a system mounted media "
445 media->iown = false; // mark attachment as foreign
447 setMediaSource(media);
450 mountsucceeded = true;
458 #endif // REUSE_FOREIGN_MOUNTS
461 closeTray( it->name );
463 // try all filesystems in sequence
464 for(list<string>::iterator fsit = filesystems.begin()
465 ; !mountsucceeded && fsit != filesystems.end()
470 if( !isUseableAttachPoint(Pathname(mountpoint)))
472 mountpoint = createAttachPoint().asString();
473 setAttachPoint( mountpoint, true);
474 if( mountpoint.empty())
476 ZYPP_THROW( MediaBadAttachPointException(url()));
480 mount.mount(it->name, mountpoint, *fsit, options);
482 setMediaSource(media);
484 // wait for /etc/mtab update ...
485 // (shouldn't be needed)
487 while( !(mountsucceeded=isAttached()) && --limit)
498 setMediaSource(MediaSourceRef());
501 mount.umount(attachPoint().asString());
503 catch (const MediaException & excpt_r)
505 ZYPP_CAUGHT(excpt_r);
507 ZYPP_THROW(MediaMountException(
508 "Unable to verify that the media was mounted",
513 catch (const MediaMountException &e)
519 catch (const MediaException & excpt_r)
522 ZYPP_CAUGHT(excpt_r);
531 if( !merr.mountOutput().empty())
533 ZYPP_THROW(MediaMountException(merr.mountError(),
536 merr.mountOutput()));
540 ZYPP_THROW(MediaMountException("Mounting media failed",
541 _url.asString(), mountpoint));
544 DBG << _lastdev << " " << count << endl;
548 ///////////////////////////////////////////////////////////////////
551 // METHOD NAME : MediaCD::releaseFrom
552 // METHOD TYPE : PMError
554 // DESCRIPTION : Asserted that media is attached.
556 void MediaCD::releaseFrom( const std::string & ejectDev )
561 AttachedMedia am( attachedMedia());
562 if(am.mediaSource && am.mediaSource->iown)
563 mount.umount(am.attachPoint->path.asString());
565 catch (const Exception & excpt_r)
567 ZYPP_CAUGHT(excpt_r);
568 if (!ejectDev.empty())
570 #if FORCE_RELEASE_FOREIGN > 0
571 /* 1 = automounted only, 2 = all */
572 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
574 if(openTray( ejectDev ))
577 ZYPP_RETHROW(excpt_r);
581 if (!ejectDev.empty())
583 #if FORCE_RELEASE_FOREIGN > 0
584 /* 1 = automounted only, 2 = all */
585 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
587 if( !openTray( ejectDev ))
589 #if REPORT_EJECT_ERRORS
590 ZYPP_THROW(MediaNotEjectedException(ejectDev));
596 ///////////////////////////////////////////////////////////////////
599 // METHOD NAME : MediaCD::forceEject
600 // METHOD TYPE : void
602 // Asserted that media is not attached.
604 void MediaCD::forceEject(const std::string & ejectDev)
607 if ( !isAttached()) { // no device mounted in this instance
610 detectDevices(_url.getScheme() == "dvd" ? true : false));
614 DBG << "creating on-demand device list" << endl;
615 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
616 string device( "/dev/cdrom" );
617 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
621 PathInfo dinfo(device);
624 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
626 DeviceList::const_iterator d( detected.begin());
627 for( ; d != detected.end(); ++d)
629 // /dev/cdrom or /dev/dvd to the front
630 if( media.equals( *d))
631 _devices.push_front( *d);
633 _devices.push_back( *d);
638 // no /dev/cdrom or /dev/dvd link
643 DeviceList::iterator it;
644 for( it = _devices.begin(); it != _devices.end(); ++it ) {
645 MediaSourceRef media( new MediaSource( *it));
646 if (media->name != ejectDev)
650 PathInfo dinfo(media->name);
653 media->maj_nr = dinfo.major();
654 media->min_nr = dinfo.minor();
656 DeviceList::const_iterator d( detected.begin());
657 for( ; d != detected.end(); ++d)
659 if( media->equals( *d))
668 DBG << "skipping invalid device: " << it->name << endl;
672 // FIXME: we have also to check if it is mounted in the system
673 AttachedMedia ret( findAttachedMedia( media));
674 if( !ret.mediaSource)
676 #if FORCE_RELEASE_FOREIGN > 0
677 /* 1 = automounted only, 2 = all */
678 forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
680 if ( openTray( it->name ) )
683 break; // on 1st success
690 #if REPORT_EJECT_ERRORS
691 ZYPP_THROW(MediaNotEjectedException());
696 bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
698 bool is_automounted = false;
699 if( media.mediaSource && !media.mediaSource->name.empty())
701 using namespace zypp::target::hal;
705 HalContext hal(true);
707 HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
710 std::string udi = vol.getUDI();
716 key = "info.hal_mount.created_mount_point";
717 mnt = hal.getDevicePropertyString(udi, key);
719 if(media.attachPoint->path == mnt)
720 is_automounted = true;
722 catch(const HalException &e1)
728 key = "volume.mount_point";
729 mnt = hal.getDevicePropertyString(udi, key);
731 if(media.attachPoint->path == mnt)
732 is_automounted = true;
734 catch(const HalException &e2)
741 catch(const HalException &e)
746 DBG << "Media " << media.mediaSource->asString()
747 << " attached on " << media.attachPoint->path
748 << " is" << (is_automounted ? "" : " not")
749 << " automounted" << std::endl;
750 return is_automounted;
753 ///////////////////////////////////////////////////////////////////
755 // METHOD NAME : MediaCD::isAttached
756 // METHOD TYPE : bool
758 // DESCRIPTION : Override check if media is attached.
761 MediaCD::isAttached() const
763 return checkAttached(false);
766 ///////////////////////////////////////////////////////////////////
768 // METHOD NAME : MediaCD::getFile
769 // METHOD TYPE : PMError
771 // DESCRIPTION : Asserted that media is attached.
773 void MediaCD::getFile( const Pathname & filename ) const
775 MediaHandler::getFile( filename );
778 ///////////////////////////////////////////////////////////////////
780 // METHOD NAME : MediaCD::getDir
781 // METHOD TYPE : PMError
783 // DESCRIPTION : Asserted that media is attached.
785 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
787 MediaHandler::getDir( dirname, recurse_r );
790 ///////////////////////////////////////////////////////////////////
793 // METHOD NAME : MediaCD::getDirInfo
794 // METHOD TYPE : PMError
796 // DESCRIPTION : Asserted that media is attached and retlist is empty.
798 void MediaCD::getDirInfo( std::list<std::string> & retlist,
799 const Pathname & dirname, bool dots ) const
801 MediaHandler::getDirInfo( retlist, dirname, dots );
804 ///////////////////////////////////////////////////////////////////
807 // METHOD NAME : MediaCD::getDirInfo
808 // METHOD TYPE : PMError
810 // DESCRIPTION : Asserted that media is attached and retlist is empty.
812 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
813 const Pathname & dirname, bool dots ) const
815 MediaHandler::getDirInfo( retlist, dirname, dots );
818 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
820 return MediaHandler::getDoesFileExist( filename );
823 bool MediaCD::hasMoreDevices()
825 if (_devices.size() == 0)
827 else if (_lastdev_tried < 0)
830 return (unsigned) _lastdev_tried < _devices.size() - 1;
834 MediaCD::getDetectedDevices(std::vector<std::string> & devices,
835 unsigned int & index) const
838 if (!devices.empty())
841 for (DeviceList::const_iterator it = _devices.begin();
842 it != _devices.end(); ++it)
843 devices.push_back(it->name);
848 // try to detect again if _devices are empty (maybe this method will be
849 // called before _devices get actually filled)
852 DBG << "no device list so far, trying to detect" << endl;
855 detectDevices(_url.getScheme() == "dvd" ? true : false));
857 for (DeviceList::const_iterator it = detected.begin();
858 it != detected.end(); ++it)
859 devices.push_back(it->name);
861 // don't know which one is in use in this case
865 MIL << "got " << devices.size() << " detected devices, current: "
866 << (index < devices.size() ? devices[index] : "<none>")
867 << "(" << index << ")" << endl;
873 // vim: set ts=8 sts=2 sw=2 ai noet: