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"
72 bool isNewDevice(const std::list<MediaSource> &devices,
73 const MediaSource &media)
75 std::list<MediaSource>::const_iterator d( devices.begin());
76 for( ; d != devices.end(); ++d)
78 if( media.equals( *d))
84 inline Pathname get_sysfs_path()
87 if(::getuid() == ::geteuid() && ::getgid() == ::getegid())
89 const char *env = ::getenv("SYSFS_PATH");
93 if( PathInfo(sysfs_path, PathInfo::LSTAT).isDir())
98 if( PathInfo(sysfs_path, PathInfo::LSTAT).isDir())
107 ///////////////////////////////////////////////////////////////////
109 // CLASS NAME : MediaCD
111 ///////////////////////////////////////////////////////////////////
113 ///////////////////////////////////////////////////////////////////
116 // METHOD NAME : MediaCD::MediaCD
117 // METHOD TYPE : Constructor
121 MediaCD::MediaCD( const Url & url_r,
122 const Pathname & attach_point_hint_r )
123 : MediaHandler( url_r, attach_point_hint_r,
124 url_r.getPathName(), // urlpath below attachpoint
129 MIL << "MediaCD::MediaCD(" << url_r << ", "
130 << attach_point_hint_r << ")" << endl;
132 if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
134 ERR << "Unsupported schema in the Url: " << url_r.asString()
136 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
140 DeviceList detected( detectDevices(
141 url_r.getScheme() == "dvd" ? true : false
145 string devices = _url.getQueryParam("devices");
146 if (!devices.empty())
148 string::size_type pos;
149 DBG << "parse " << devices << endl;
150 while(!devices.empty())
152 pos = devices.find(',');
153 string device = devices.substr(0,pos);
157 MediaSource media("cdrom", device, 0, 0);
158 _devices.push_back( media);
159 DBG << "use device (delayed verify)" << device << endl;
162 PathInfo dinfo(device);
165 MediaSource media("cdrom", device, dinfo.major(),
167 DeviceList::const_iterator d( detected.begin());
168 for( ; d != detected.end(); ++d)
170 if( media.equals( *d))
173 _devices.push_back( *d);
174 DBG << "use device " << device << endl;
181 ERR << "Device " << device << " is not acceptable "
182 << "for " << _url.getScheme() << std::endl;
183 ZYPP_THROW(MediaBadUrlException(_url,
184 "Invalid device name in URL devices argument"
189 if (pos!=string::npos)
190 devices=devices.substr(pos+1);
198 DBG << "going to use on-demand device list" << endl;
201 DBG << "going to use default device list" << endl;
202 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
203 string device( "/dev/cdrom" );
204 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
208 PathInfo dinfo(device);
211 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
213 DeviceList::const_iterator d( detected.begin());
214 for( ; d != detected.end(); ++d)
216 // /dev/cdrom or /dev/dvd to the front
217 if( media.equals( *d))
218 _devices.push_front( *d);
220 _devices.push_back( *d);
225 // no /dev/cdrom or /dev/dvd link
231 if( _devices.empty())
233 ERR << "Unable to find any cdrom drive for " << _url.asString()
235 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
239 ///////////////////////////////////////////////////////////////////
242 // METHOD NAME : MediaCD::openTray
243 // METHOD TYPE : bool
245 bool MediaCD::openTray( const std::string & device_r )
247 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
252 res = ::ioctl( fd, CDROMEJECT );
260 WAR << "Unable to open '" << device_r
261 << "' (" << ::strerror( errno ) << ")" << endl;
265 WAR << "Eject " << device_r
266 << " failed (" << ::strerror( errno ) << ")" << endl;
269 #if defined(EJECT_TOOL_PATH)
270 DBG << "Try to eject " << device_r << " using "
271 << EJECT_TOOL_PATH << " utility" << std::endl;
274 cmd[0] = EJECT_TOOL_PATH;
275 cmd[1] = device_r.c_str();
277 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
279 for(std::string out( eject.receiveLine());
280 out.length(); out = eject.receiveLine())
285 if(eject.close() != 0)
287 WAR << "Eject of " << device_r << " failed." << std::endl;
294 MIL << "Eject of " << device_r << " successful." << endl;
298 ///////////////////////////////////////////////////////////////////
301 // METHOD NAME : MediaCD::closeTray
302 // METHOD TYPE : bool
304 bool MediaCD::closeTray( const std::string & device_r )
306 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
308 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
311 int res = ::ioctl( fd, CDROMCLOSETRAY );
314 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
317 DBG << "Close tray " << device_r << endl;
321 ///////////////////////////////////////////////////////////////////
324 // METHOD NAME : MediaCD::detectDevices
325 // METHOD TYPE : MediaCD::DeviceList
328 MediaCD::detectDevices(bool supportingDVD)
330 using namespace zypp::target::hal;
335 HalContext hal(true);
337 std::vector<std::string> drv_udis;
338 drv_udis = hal.findDevicesByCapability("storage.cdrom");
340 DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
341 for(size_t d = 0; d < drv_udis.size(); d++)
343 HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
347 bool supportsDVD=false;
350 std::vector<std::string> caps;
352 caps = drv.getCdromCapabilityNames();
354 catch(const HalException &e)
359 std::vector<std::string>::const_iterator ci;
360 for( ci=caps.begin(); ci != caps.end(); ++ci)
367 MediaSource media("cdrom", drv.getDeviceFile(),
368 drv.getDeviceMajor(),
369 drv.getDeviceMinor());
370 DBG << "Found " << drv_udis[d] << ": "
371 << media.asString() << std::endl;
372 if( supportingDVD && supportsDVD)
374 detected.push_front(media);
378 detected.push_back(media);
383 catch(const zypp::target::hal::HalException &e)
390 // Hal does not include SCSI / Virtual CDROMs on iSeries ...
392 // Hmm... always? We can't detect DVD here.
393 if( detected.empty())
395 Pathname sysfs_path( get_sysfs_path());
396 if(sysfs_path.empty())
399 std::string sys_name;
400 std::string dev_name;
402 // SCSI cdrom devices (/dev/sr0, ...)
403 sys_name = sysfs_path.cat("block/sr").asString();
404 dev_name = "/dev/sr";
405 DBG << "Collecting SCSI CD-ROM devices ("
406 << dev_name << "X)" << std::endl;
407 for(size_t i=0; i < 16; i++)
409 PathInfo sys_info(sys_name + str::numstring(i));
410 PathInfo dev_info(dev_name + str::numstring(i));
411 if( sys_info.isDir() && dev_info.isBlk())
413 // Hmm.. how to check if it supports DVDs?
414 MediaSource media("cdrom", dev_info.asString(),
417 if( isNewDevice(detected, media))
419 DBG << "Found SCSI CDROM "
422 detected.push_back(media);
427 // IBM iSeries virtual CD-ROM devices (how many?)
429 sys_name = sysfs_path.cat("block/iseries!vcd").asString();
430 dev_name = "/dev/iseries/vcd";
431 DBG << "Collecting iSeries virtual CD-ROM devices ("
432 << dev_name << "X)" << std::endl;
433 for(size_t i=0; i < 8; i++)
435 char drive_letter = 'a' + i;
436 PathInfo sys_info(sys_name + drive_letter);
437 PathInfo dev_info(dev_name + drive_letter);
438 if( sys_info.isDir() && dev_info.isBlk())
440 // Hmm.. how to check if it supports DVDs?
441 MediaSource media("cdrom", dev_info.asString(),
444 if( isNewDevice(detected, media))
446 DBG << "Found iSeries virtual CDROM "
449 detected.push_back(media);
455 // Other device types?
461 ///////////////////////////////////////////////////////////////////
464 // METHOD NAME : MediaCD::attachTo
465 // METHOD TYPE : PMError
467 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
469 void MediaCD::attachTo(bool next)
471 DBG << "next " << next << " last " << _lastdev << endl;
472 if (next && _lastdev == -1)
473 ZYPP_THROW(MediaNotSupportedException(url()));
476 DeviceList detected( detectDevices(
477 _url.getScheme() == "dvd" ? true : false
482 DBG << "creating on-demand device list" << endl;
483 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
484 string device( "/dev/cdrom" );
485 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
489 PathInfo dinfo(device);
492 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
494 DeviceList::const_iterator d( detected.begin());
495 for( ; d != detected.end(); ++d)
497 // /dev/cdrom or /dev/dvd to the front
498 if( media.equals( *d))
499 _devices.push_front( *d);
501 _devices.push_back( *d);
506 // no /dev/cdrom or /dev/dvd link
513 string mountpoint = attachPoint().asString();
514 bool mountsucceeded = false;
516 MediaMountException merr;
518 string options = _url.getQueryParam("mountoptions");
524 //TODO: make configurable
525 list<string> filesystems;
527 // if DVD, try UDF filesystem before iso9660
528 if ( _url.getScheme() == "dvd" )
529 filesystems.push_back("udf");
531 filesystems.push_back("iso9660");
533 // try all devices in sequence
534 for (DeviceList::iterator it = _devices.begin()
535 ; !mountsucceeded && it != _devices.end()
538 DBG << "count " << count << endl;
539 if (next && count<=_lastdev )
541 DBG << "skipping device " << it->name << endl;
545 MediaSource temp( *it);
547 PathInfo dinfo(temp.name);
550 temp.maj_nr = dinfo.major();
551 temp.min_nr = dinfo.minor();
553 DeviceList::const_iterator d( detected.begin());
554 for( ; d != detected.end(); ++d)
556 if( temp.equals( *d))
565 DBG << "skipping invalid device: " << it->name << endl;
568 MediaSourceRef media( new MediaSource(temp));
570 MediaSourceRef media( new MediaSource( *it));
573 AttachedMedia ret( findAttachedMedia( media));
575 if( ret.mediaSource && ret.attachPoint &&
576 !ret.attachPoint->empty())
578 DBG << "Using a shared media "
579 << ret.mediaSource->name
581 << ret.attachPoint->path
584 setAttachPoint(ret.attachPoint);
585 setMediaSource(ret.mediaSource);
587 mountsucceeded = true;
591 #if REUSE_FOREIGN_MOUNTS > 0
593 MediaManager manager;
594 MountEntries entries( manager.getMountEntries());
595 MountEntries::const_iterator e;
596 for( e = entries.begin(); e != entries.end(); ++e)
598 bool is_device = false;
599 std::string dev_path(Pathname(e->src).asString());
602 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
603 dev_info(e->src) && dev_info.isBlk())
608 if( is_device && media->maj_nr == dev_info.major() &&
609 media->min_nr == dev_info.minor())
611 AttachPointRef ap( new AttachPoint(e->dir, false));
612 AttachedMedia am( media, ap);
614 // 1 = automounted only, 2 == all
616 #if REUSE_FOREIGN_MOUNTS == 1
617 if( isAutoMountedMedia(am))
620 DBG << "Using a system mounted media "
626 media->iown = false; // mark attachment as foreign
628 setMediaSource(media);
631 mountsucceeded = true;
639 #endif // REUSE_FOREIGN_MOUNTS
642 closeTray( it->name );
644 // try all filesystems in sequence
645 for(list<string>::iterator fsit = filesystems.begin()
646 ; !mountsucceeded && fsit != filesystems.end()
650 if( !isUseableAttachPoint(Pathname(mountpoint)))
652 mountpoint = createAttachPoint().asString();
653 setAttachPoint( mountpoint, true);
654 if( mountpoint.empty())
656 ZYPP_THROW( MediaBadAttachPointException(url()));
660 mount.mount(it->name, mountpoint, *fsit, options);
662 setMediaSource(media);
664 // wait for /etc/mtab update ...
665 // (shouldn't be needed)
667 while( !(mountsucceeded=isAttached()) && --limit)
678 setMediaSource(MediaSourceRef());
681 mount.umount(attachPoint().asString());
683 catch (const MediaException & excpt_r)
685 ZYPP_CAUGHT(excpt_r);
687 ZYPP_THROW(MediaMountException(
688 "Unable to verify that the media was mounted",
693 catch (const MediaMountException &e)
699 catch (const MediaException & excpt_r)
702 ZYPP_CAUGHT(excpt_r);
711 if( !merr.mountOutput().empty())
713 ZYPP_THROW(MediaMountException(merr.mountError(),
716 merr.mountOutput()));
720 ZYPP_THROW(MediaMountException("Mounting media failed",
721 _url.asString(), mountpoint));
724 DBG << _lastdev << " " << count << endl;
728 ///////////////////////////////////////////////////////////////////
731 // METHOD NAME : MediaCD::releaseFrom
732 // METHOD TYPE : PMError
734 // DESCRIPTION : Asserted that media is attached.
736 void MediaCD::releaseFrom( bool eject )
740 AttachedMedia am( attachedMedia());
741 if(am.mediaSource && am.mediaSource->iown)
742 mount.umount(am.attachPoint->path.asString());
744 catch (const Exception & excpt_r)
746 ZYPP_CAUGHT(excpt_r);
749 #if FORCE_RELEASE_FOREIGN > 0
750 /* 1 = automounted only, 2 = all */
751 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
753 if(openTray( mediaSourceName()))
756 ZYPP_RETHROW(excpt_r);
762 #if FORCE_RELEASE_FOREIGN > 0
763 /* 1 = automounted only, 2 = all */
764 forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
766 if( !openTray( mediaSourceName() ))
768 #if REPORT_EJECT_ERRORS
769 ZYPP_THROW(MediaNotEjectedException(mediaSourceName()));
775 ///////////////////////////////////////////////////////////////////
778 // METHOD NAME : MediaCD::forceEject
779 // METHOD TYPE : void
781 // Asserted that media is not attached.
783 void MediaCD::forceEject()
786 if ( !isAttached()) { // no device mounted in this instance
788 DeviceList detected( detectDevices(
789 _url.getScheme() == "dvd" ? true : false
794 DBG << "creating on-demand device list" << endl;
795 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
796 string device( "/dev/cdrom" );
797 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
801 PathInfo dinfo(device);
804 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
806 DeviceList::const_iterator d( detected.begin());
807 for( ; d != detected.end(); ++d)
809 // /dev/cdrom or /dev/dvd to the front
810 if( media.equals( *d))
811 _devices.push_front( *d);
813 _devices.push_back( *d);
818 // no /dev/cdrom or /dev/dvd link
824 DeviceList::iterator it;
825 for( it = _devices.begin(); it != _devices.end(); ++it ) {
826 MediaSourceRef media( new MediaSource( *it));
829 PathInfo dinfo(media->name);
832 media->maj_nr = dinfo.major();
833 media->min_nr = dinfo.minor();
835 DeviceList::const_iterator d( detected.begin());
836 for( ; d != detected.end(); ++d)
838 if( media->equals( *d))
847 DBG << "skipping invalid device: " << it->name << endl;
852 // FIXME: we have also to check if it is mounted in the system
853 AttachedMedia ret( findAttachedMedia( media));
854 if( !ret.mediaSource)
856 #if FORCE_RELEASE_FOREIGN > 0
857 /* 1 = automounted only, 2 = all */
858 forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
860 if ( openTray( it->name ) )
863 break; // on 1st success
870 #if REPORT_EJECT_ERRORS
871 ZYPP_THROW(MediaNotEjectedException());
876 bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
878 bool is_automounted = false;
879 if( media.mediaSource && !media.mediaSource->name.empty())
881 using namespace zypp::target::hal;
885 HalContext hal(true);
887 HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
890 std::string udi = vol.getUDI();
896 key = "info.hal_mount.created_mount_point";
897 mnt = hal.getDevicePropertyString(udi, key);
899 if(media.attachPoint->path == mnt)
900 is_automounted = true;
902 catch(const HalException &e1)
908 key = "volume.mount_point";
909 mnt = hal.getDevicePropertyString(udi, key);
911 if(media.attachPoint->path == mnt)
912 is_automounted = true;
914 catch(const HalException &e2)
921 catch(const HalException &e)
926 DBG << "Media " << media.mediaSource->asString()
927 << " attached on " << media.attachPoint->path
928 << " is" << (is_automounted ? "" : " not")
929 << " automounted" << std::endl;
930 return is_automounted;
933 ///////////////////////////////////////////////////////////////////
935 // METHOD NAME : MediaCD::isAttached
936 // METHOD TYPE : bool
938 // DESCRIPTION : Override check if media is attached.
941 MediaCD::isAttached() const
943 return checkAttached(false);
946 ///////////////////////////////////////////////////////////////////
948 // METHOD NAME : MediaCD::getFile
949 // METHOD TYPE : PMError
951 // DESCRIPTION : Asserted that media is attached.
953 void MediaCD::getFile( const Pathname & filename ) const
955 MediaHandler::getFile( filename );
958 ///////////////////////////////////////////////////////////////////
960 // METHOD NAME : MediaCD::getDir
961 // METHOD TYPE : PMError
963 // DESCRIPTION : Asserted that media is attached.
965 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
967 MediaHandler::getDir( dirname, recurse_r );
970 ///////////////////////////////////////////////////////////////////
973 // METHOD NAME : MediaCD::getDirInfo
974 // METHOD TYPE : PMError
976 // DESCRIPTION : Asserted that media is attached and retlist is empty.
978 void MediaCD::getDirInfo( std::list<std::string> & retlist,
979 const Pathname & dirname, bool dots ) const
981 MediaHandler::getDirInfo( retlist, dirname, dots );
984 ///////////////////////////////////////////////////////////////////
987 // METHOD NAME : MediaCD::getDirInfo
988 // METHOD TYPE : PMError
990 // DESCRIPTION : Asserted that media is attached and retlist is empty.
992 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
993 const Pathname & dirname, bool dots ) const
995 MediaHandler::getDirInfo( retlist, dirname, dots );
998 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
1000 return MediaHandler::getDoesFileExist( filename );
1003 } // namespace media
1005 // vim: set ts=8 sts=2 sw=2 ai noet: