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"
22 #include <cstring> // strerror
23 #include <cstdlib> // getenv
28 #include <sys/ioctl.h>
31 #include <unistd.h> // geteuid, ...
33 #include <linux/cdrom.h>
38 ** try umount of foreign (user/automounter) media on eject
39 ** 0 = don't force, 1 = automounted only, 2 == all
41 #define FORCE_RELEASE_FOREIGN 2
44 ** Reuse foreign (user/automounter) mount points.
45 ** 0 = don't use, 1 = automounted only, 2 = all
47 #define REUSE_FOREIGN_MOUNTS 2
50 ** if to throw exception on eject errors or ignore them
52 #define REPORT_EJECT_ERRORS 1
55 ** If defined to the full path of the eject utility,
56 ** it will be used additionally to the eject-ioctl.
58 #define EJECT_TOOL_PATH "/bin/eject"
66 ///////////////////////////////////////////////////////////////////
68 // CLASS NAME : MediaCD
70 ///////////////////////////////////////////////////////////////////
73 ///////////////////////////////////////////////////////////////////
76 // METHOD NAME : MediaCD::MediaCD
77 // METHOD TYPE : Constructor
81 MediaCD::MediaCD( const Url & url_r,
82 const Pathname & attach_point_hint_r )
83 : MediaHandler( url_r, attach_point_hint_r,
84 url_r.getPathName(), // urlpath below attachpoint
86 _lastdev(-1), _lastdev_tried(-1)
88 MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")"
91 if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
93 ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
94 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
97 string devices = _url.getQueryParam("devices");
100 string::size_type pos;
101 DBG << "parse " << devices << endl;
102 while(!devices.empty())
104 pos = devices.find(',');
105 string device = devices.substr(0,pos);
108 MediaSource media("cdrom", device, 0, 0);
109 _devices.push_back( media);
110 DBG << "use device (delayed verify)" << device << endl;
112 if (pos!=string::npos)
113 devices=devices.substr(pos+1);
120 DBG << "going to use on-demand device list" << endl;
124 if( _devices.empty())
126 ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
127 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
131 ///////////////////////////////////////////////////////////////////
134 // METHOD NAME : MediaCD::openTray
135 // METHOD TYPE : bool
137 bool MediaCD::openTray( const std::string & device_r )
139 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
144 res = ::ioctl( fd, CDROMEJECT );
152 WAR << "Unable to open '" << device_r
153 << "' (" << ::strerror( errno ) << ")" << endl;
157 WAR << "Eject " << device_r
158 << " failed (" << ::strerror( errno ) << ")" << endl;
161 #if defined(EJECT_TOOL_PATH)
162 DBG << "Try to eject " << device_r << " using "
163 << EJECT_TOOL_PATH << " utility" << std::endl;
166 cmd[0] = EJECT_TOOL_PATH;
167 cmd[1] = device_r.c_str();
169 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
171 for(std::string out( eject.receiveLine());
172 out.length(); out = eject.receiveLine())
177 if(eject.close() != 0)
179 WAR << "Eject of " << device_r << " failed." << std::endl;
186 MIL << "Eject of " << device_r << " successful." << endl;
190 ///////////////////////////////////////////////////////////////////
193 // METHOD NAME : MediaCD::closeTray
194 // METHOD TYPE : bool
196 bool MediaCD::closeTray( const std::string & device_r )
198 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
200 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
203 int res = ::ioctl( fd, CDROMCLOSETRAY );
206 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
209 DBG << "Close tray " << device_r << endl;
213 ///////////////////////////////////////////////////////////////////
216 // METHOD NAME : MediaCD::detectDevices
217 // METHOD TYPE : MediaCD::DeviceList
220 MediaCD::detectDevices(bool supportingDVD) const
224 #warning Poor CDROM devices detection without HAL
225 /** \todo rewite using e.g. 'hwinfo --cdrom' or libudev */
226 WAR << "Cdrom drive detection without HAL! " << std::endl;
227 PathInfo dvdinfo( "/dev/dvd" );
228 PathInfo cdrinfo( "/dev/cdrom" );
229 if ( dvdinfo.isBlk() )
231 MediaSource media( "cdrom", dvdinfo.path().asString(), dvdinfo.major(), dvdinfo.minor() );
232 DBG << "Found (NO_HAL): " << media << std::endl;
233 detected.push_back( media );
236 && ! ( cdrinfo.major() == dvdinfo.major() && cdrinfo.minor() == dvdinfo.minor() ) )
238 MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.major(), cdrinfo.minor() );
239 DBG << "Found (NO_HAL): " << media << std::endl;
240 detected.push_back( media );
247 ///////////////////////////////////////////////////////////////////
250 // METHOD NAME : MediaCD::attachTo
251 // METHOD TYPE : PMError
253 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
255 void MediaCD::attachTo(bool next)
257 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
258 if (next && _lastdev == -1)
259 ZYPP_THROW(MediaNotSupportedException(url()));
262 detectDevices(_url.getScheme() == "dvd" ? true : false));
266 DBG << "creating on-demand device list" << endl;
267 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
268 string device( "/dev/cdrom" );
269 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
274 PathInfo dinfo(device);
277 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
279 DeviceList::const_iterator d( detected.begin());
280 for( ; d != detected.end(); ++d)
282 // /dev/cdrom or /dev/dvd to the front
283 if( media.equals( *d))
284 _devices.push_front( *d);
286 _devices.push_back( *d);
291 // no /dev/cdrom or /dev/dvd link
297 string mountpoint = attachPoint().asString();
298 bool mountsucceeded = false;
300 MediaMountException merr;
302 string options = _url.getQueryParam("mountoptions");
308 //TODO: make configurable
309 list<string> filesystems;
311 // if DVD, try UDF filesystem before iso9660
312 if ( _url.getScheme() == "dvd" )
313 filesystems.push_back("udf");
315 filesystems.push_back("iso9660");
317 // try all devices in sequence
318 for (DeviceList::iterator it = _devices.begin()
319 ; !mountsucceeded && it != _devices.end()
322 DBG << "count " << count << endl;
323 if (next && count <=_lastdev_tried )
325 DBG << "skipping device " << it->name << endl;
329 _lastdev_tried = count;
331 MediaSource temp( *it);
333 PathInfo dinfo(temp.name);
336 temp.maj_nr = dinfo.major();
337 temp.min_nr = dinfo.minor();
339 DeviceList::const_iterator d( detected.begin());
340 for( ; d != detected.end(); ++d)
342 if( temp.equals( *d))
351 DBG << "skipping invalid device: " << it->name << endl;
355 MediaSourceRef media( new MediaSource(temp));
356 AttachedMedia ret( findAttachedMedia( media));
358 if( ret.mediaSource && ret.attachPoint &&
359 !ret.attachPoint->empty())
361 DBG << "Using a shared media "
362 << ret.mediaSource->name
364 << ret.attachPoint->path
367 setAttachPoint(ret.attachPoint);
368 setMediaSource(ret.mediaSource);
370 mountsucceeded = true;
374 #if REUSE_FOREIGN_MOUNTS > 0
376 MediaManager manager;
377 MountEntries entries( manager.getMountEntries());
378 MountEntries::const_iterator e;
379 for( e = entries.begin(); e != entries.end(); ++e)
381 bool is_device = false;
382 std::string dev_path(Pathname(e->src).asString());
385 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
386 dev_info(e->src) && dev_info.isBlk())
391 if( is_device && media->maj_nr == dev_info.major() &&
392 media->min_nr == dev_info.minor())
394 AttachPointRef ap( new AttachPoint(e->dir, false));
395 AttachedMedia am( media, ap);
397 // 1 = automounted only, 2 == all
399 #if REUSE_FOREIGN_MOUNTS == 1
400 if( isAutoMountedMedia(am))
403 DBG << "Using a system mounted media "
409 media->iown = false; // mark attachment as foreign
411 setMediaSource(media);
414 mountsucceeded = true;
422 #endif // REUSE_FOREIGN_MOUNTS
425 closeTray( it->name );
427 // try all filesystems in sequence
428 for(list<string>::iterator fsit = filesystems.begin()
429 ; !mountsucceeded && fsit != filesystems.end()
434 if( !isUseableAttachPoint(Pathname(mountpoint)))
436 mountpoint = createAttachPoint().asString();
437 setAttachPoint( mountpoint, true);
438 if( mountpoint.empty())
440 ZYPP_THROW( MediaBadAttachPointException(url()));
444 mount.mount(it->name, mountpoint, *fsit, options);
446 setMediaSource(media);
448 // wait for /etc/mtab update ...
449 // (shouldn't be needed)
451 while( !(mountsucceeded=isAttached()) && --limit)
462 setMediaSource(MediaSourceRef());
465 mount.umount(attachPoint().asString());
467 catch (const MediaException & excpt_r)
469 ZYPP_CAUGHT(excpt_r);
471 ZYPP_THROW(MediaMountException(
472 "Unable to verify that the media was mounted",
477 catch (const MediaMountException &e)
483 catch (const MediaException & excpt_r)
486 ZYPP_CAUGHT(excpt_r);
495 if( !merr.mountOutput().empty())
497 ZYPP_THROW(MediaMountException(merr.mountError(),
500 merr.mountOutput()));
504 ZYPP_THROW(MediaMountException("Mounting media failed",
505 _url.asString(), mountpoint));
508 DBG << _lastdev << " " << count << endl;
512 ///////////////////////////////////////////////////////////////////
515 // METHOD NAME : MediaCD::releaseFrom
516 // METHOD TYPE : PMError
518 // DESCRIPTION : Asserted that media is attached.
520 void MediaCD::releaseFrom( const std::string & ejectDev )
525 AttachedMedia am( attachedMedia());
526 if(am.mediaSource && am.mediaSource->iown)
527 mount.umount(am.attachPoint->path.asString());
529 catch (const Exception & excpt_r)
531 ZYPP_CAUGHT(excpt_r);
532 if (!ejectDev.empty())
534 #if FORCE_RELEASE_FOREIGN > 0
535 /* 1 = automounted only, 2 = all */
536 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
538 if(openTray( ejectDev ))
541 ZYPP_RETHROW(excpt_r);
545 if (!ejectDev.empty())
547 #if FORCE_RELEASE_FOREIGN > 0
548 /* 1 = automounted only, 2 = all */
549 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
551 if( !openTray( ejectDev ))
553 #if REPORT_EJECT_ERRORS
554 ZYPP_THROW(MediaNotEjectedException(ejectDev));
560 ///////////////////////////////////////////////////////////////////
563 // METHOD NAME : MediaCD::forceEject
564 // METHOD TYPE : void
566 // Asserted that media is not attached.
568 void MediaCD::forceEject(const std::string & ejectDev)
571 if ( !isAttached()) { // no device mounted in this instance
574 detectDevices(_url.getScheme() == "dvd" ? true : false));
578 DBG << "creating on-demand device list" << endl;
579 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
580 string device( "/dev/cdrom" );
581 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
585 PathInfo dinfo(device);
588 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
590 DeviceList::const_iterator d( detected.begin());
591 for( ; d != detected.end(); ++d)
593 // /dev/cdrom or /dev/dvd to the front
594 if( media.equals( *d))
595 _devices.push_front( *d);
597 _devices.push_back( *d);
602 // no /dev/cdrom or /dev/dvd link
607 DeviceList::iterator it;
608 for( it = _devices.begin(); it != _devices.end(); ++it ) {
609 MediaSourceRef media( new MediaSource( *it));
610 if (media->name != ejectDev)
614 PathInfo dinfo(media->name);
617 media->maj_nr = dinfo.major();
618 media->min_nr = dinfo.minor();
620 DeviceList::const_iterator d( detected.begin());
621 for( ; d != detected.end(); ++d)
623 if( media->equals( *d))
632 DBG << "skipping invalid device: " << it->name << endl;
636 // FIXME: we have also to check if it is mounted in the system
637 AttachedMedia ret( findAttachedMedia( media));
638 if( !ret.mediaSource)
640 #if FORCE_RELEASE_FOREIGN > 0
641 /* 1 = automounted only, 2 = all */
642 forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
644 if ( openTray( it->name ) )
647 break; // on 1st success
654 #if REPORT_EJECT_ERRORS
655 ZYPP_THROW(MediaNotEjectedException());
660 bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
662 bool is_automounted = false;
663 if( media.mediaSource && !media.mediaSource->name.empty())
666 using namespace zypp::target::hal;
669 HalContext hal(true);
671 HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
674 std::string udi = vol.getUDI();
680 key = "info.hal_mount.created_mount_point";
681 mnt = hal.getDevicePropertyString(udi, key);
683 if(media.attachPoint->path == mnt)
684 is_automounted = true;
686 catch(const HalException &e1)
692 key = "volume.mount_point";
693 mnt = hal.getDevicePropertyString(udi, key);
695 if(media.attachPoint->path == mnt)
696 is_automounted = true;
698 catch(const HalException &e2)
705 catch(const HalException &e)
710 #warning Can not detect automounted media without HAL
711 INT << "Can not detect automounted media without HAL!" << endl;
712 // ma@: This codepath is probably unused due to 'REUSE_FOREIGN_MOUNTS == 2'
713 // Maybe we should cleanup all this automount-specail-handling.
716 DBG << "Media " << media.mediaSource->asString()
717 << " attached on " << media.attachPoint->path
718 << " is" << (is_automounted ? "" : " not")
719 << " automounted" << std::endl;
720 return is_automounted;
723 ///////////////////////////////////////////////////////////////////
725 // METHOD NAME : MediaCD::isAttached
726 // METHOD TYPE : bool
728 // DESCRIPTION : Override check if media is attached.
731 MediaCD::isAttached() const
733 return checkAttached(false);
736 ///////////////////////////////////////////////////////////////////
738 // METHOD NAME : MediaCD::getFile
739 // METHOD TYPE : PMError
741 // DESCRIPTION : Asserted that media is attached.
743 void MediaCD::getFile( const Pathname & filename ) const
745 MediaHandler::getFile( filename );
748 ///////////////////////////////////////////////////////////////////
750 // METHOD NAME : MediaCD::getDir
751 // METHOD TYPE : PMError
753 // DESCRIPTION : Asserted that media is attached.
755 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
757 MediaHandler::getDir( dirname, recurse_r );
760 ///////////////////////////////////////////////////////////////////
763 // METHOD NAME : MediaCD::getDirInfo
764 // METHOD TYPE : PMError
766 // DESCRIPTION : Asserted that media is attached and retlist is empty.
768 void MediaCD::getDirInfo( std::list<std::string> & retlist,
769 const Pathname & dirname, bool dots ) const
771 MediaHandler::getDirInfo( retlist, dirname, dots );
774 ///////////////////////////////////////////////////////////////////
777 // METHOD NAME : MediaCD::getDirInfo
778 // METHOD TYPE : PMError
780 // DESCRIPTION : Asserted that media is attached and retlist is empty.
782 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
783 const Pathname & dirname, bool dots ) const
785 MediaHandler::getDirInfo( retlist, dirname, dots );
788 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
790 return MediaHandler::getDoesFileExist( filename );
793 bool MediaCD::hasMoreDevices()
795 if (_devices.size() == 0)
797 else if (_lastdev_tried < 0)
800 return (unsigned) _lastdev_tried < _devices.size() - 1;
804 MediaCD::getDetectedDevices(std::vector<std::string> & devices,
805 unsigned int & index) const
808 if (!devices.empty())
811 for (DeviceList::const_iterator it = _devices.begin();
812 it != _devices.end(); ++it)
813 devices.push_back(it->name);
818 // try to detect again if _devices are empty (maybe this method will be
819 // called before _devices get actually filled)
822 DBG << "no device list so far, trying to detect" << endl;
825 detectDevices(_url.getScheme() == "dvd" ? true : false));
827 for (DeviceList::const_iterator it = detected.begin();
828 it != detected.end(); ++it)
829 devices.push_back(it->name);
831 // don't know which one is in use in this case
835 MIL << "got " << devices.size() << " detected devices, current: "
836 << (index < devices.size() ? devices[index] : "<none>")
837 << "(" << index << ")" << endl;
843 // vim: set ts=8 sts=2 sw=2 ai noet: