1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCD.cc
14 #include <sys/ioctl.h>
15 #include <linux/cdrom.h>
19 #include <cstring> // strerror
20 #include <cstdlib> // getenv
23 #include "zypp/base/Logger.h"
24 #include "zypp/ExternalProgram.h"
25 #include "zypp/media/Mount.h"
26 #include "zypp/media/MediaCD.h"
27 #include "zypp/media/MediaManager.h"
29 #include "zypp/AutoDispose.h"
34 ** if to throw exception on eject errors or ignore them
36 #define REPORT_EJECT_ERRORS 1
39 ** If defined to the full path of the eject utility,
40 ** it will be used additionally to the eject-ioctl.
42 #define EJECT_TOOL_PATH "/bin/eject"
50 ///////////////////////////////////////////////////////////////////
52 // CLASS NAME : MediaCD
54 ///////////////////////////////////////////////////////////////////
57 ///////////////////////////////////////////////////////////////////
60 // METHOD NAME : MediaCD::MediaCD
61 // METHOD TYPE : Constructor
65 MediaCD::MediaCD( const Url & url_r,
66 const Pathname & attach_point_hint_r )
67 : MediaHandler( url_r, attach_point_hint_r,
68 url_r.getPathName(), // urlpath below attachpoint
70 _lastdev(-1), _lastdev_tried(-1)
72 MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")"
75 if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
77 ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
78 ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
81 string devices = _url.getQueryParam("devices");
84 string::size_type pos;
85 DBG << "parse " << devices << endl;
86 while(!devices.empty())
88 pos = devices.find(',');
89 string device = devices.substr(0,pos);
92 MediaSource media("cdrom", device, 0, 0);
93 _devices.push_back( media);
94 DBG << "use device (delayed verify)" << device << endl;
96 if (pos!=string::npos)
97 devices=devices.substr(pos+1);
104 DBG << "going to use on-demand device list" << endl;
108 if( _devices.empty())
110 ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
111 ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
115 ///////////////////////////////////////////////////////////////////
118 // METHOD NAME : MediaCD::openTray
119 // METHOD TYPE : bool
121 bool MediaCD::openTray( const std::string & device_r )
123 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
128 res = ::ioctl( fd, CDROMEJECT );
136 WAR << "Unable to open '" << device_r
137 << "' (" << ::strerror( errno ) << ")" << endl;
141 WAR << "Eject " << device_r
142 << " failed (" << ::strerror( errno ) << ")" << endl;
145 #if defined(EJECT_TOOL_PATH)
146 DBG << "Try to eject " << device_r << " using "
147 << EJECT_TOOL_PATH << " utility" << std::endl;
150 cmd[0] = EJECT_TOOL_PATH;
151 cmd[1] = device_r.c_str();
153 ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
155 for(std::string out( eject.receiveLine());
156 out.length(); out = eject.receiveLine())
161 if(eject.close() != 0)
163 WAR << "Eject of " << device_r << " failed." << std::endl;
170 MIL << "Eject of " << device_r << " successful." << endl;
174 ///////////////////////////////////////////////////////////////////
177 // METHOD NAME : MediaCD::closeTray
178 // METHOD TYPE : bool
180 bool MediaCD::closeTray( const std::string & device_r )
182 int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
184 WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
187 int res = ::ioctl( fd, CDROMCLOSETRAY );
190 WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
193 DBG << "Close tray " << device_r << endl;
197 ///////////////////////////////////////////////////////////////////
200 // METHOD NAME : MediaCD::detectDevices
201 // METHOD TYPE : MediaCD::DeviceList
203 MediaCD::DeviceList MediaCD::detectDevices( bool supportingDVD ) const
205 // http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/index.html
206 zypp::AutoDispose<struct udev *> udev( ::udev_new(), ::udev_unref );
209 ERR << "Can't create udev context." << endl;
213 zypp::AutoDispose<struct udev_enumerate *> enumerate( ::udev_enumerate_new(udev), ::udev_enumerate_unref );
216 ERR << "Can't create udev list entry." << endl;
220 ::udev_enumerate_add_match_subsystem( enumerate, "block" );
221 ::udev_enumerate_add_match_property( enumerate, "ID_CDROM", "1" );
222 ::udev_enumerate_scan_devices( enumerate );
225 struct udev_list_entry * entry = 0;
226 udev_list_entry_foreach( entry, ::udev_enumerate_get_list_entry( enumerate ) )
228 zypp::AutoDispose<struct udev_device *> device( ::udev_device_new_from_syspath( ::udev_enumerate_get_udev( enumerate ),
229 ::udev_list_entry_get_name( entry ) ),
230 ::udev_device_unref );
233 ERR << "Can't create udev device." << endl;
237 if ( supportingDVD && ! ::udev_device_get_property_value( device, "ID_CDROM_DVD" ) )
239 continue; // looking for dvd only
242 const char * devnodePtr( ::udev_device_get_devnode( device ) );
245 ERR << "Got NULL devicenode." << endl;
249 // In case we need it someday:
250 //const char * mountpath = ::udev_device_get_property_value( device, "FSTAB_DIR" );
252 PathInfo devnode( devnodePtr );
253 if ( devnode.isBlk() )
255 MediaSource media( "cdrom", devnode.path().asString(), devnode.major(), devnode.minor() );
256 DBG << "Found (udev): " << media << std::endl;
257 detected.push_back( media );
260 if ( detected.empty() )
261 WAR << "Did not find any CD/DVD device." << endl;
266 ///////////////////////////////////////////////////////////////////
269 // METHOD NAME : MediaCD::attachTo
270 // METHOD TYPE : PMError
272 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
274 void MediaCD::attachTo(bool next)
276 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
277 if (next && _lastdev == -1)
278 ZYPP_THROW(MediaNotSupportedException(url()));
281 detectDevices(_url.getScheme() == "dvd" ? true : false));
285 DBG << "creating on-demand device list" << endl;
286 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
287 string device( "/dev/cdrom" );
288 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
293 PathInfo dinfo(device);
296 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
298 DeviceList::const_iterator d( detected.begin());
299 for( ; d != detected.end(); ++d)
301 // /dev/cdrom or /dev/dvd to the front
302 if( media.equals( *d))
303 _devices.push_front( *d);
305 _devices.push_back( *d);
310 // no /dev/cdrom or /dev/dvd link
316 string mountpoint = attachPoint().asString();
317 bool mountsucceeded = false;
319 MediaMountException merr;
321 string options = _url.getQueryParam("mountoptions");
327 //TODO: make configurable
328 list<string> filesystems;
330 // if DVD, try UDF filesystem before iso9660
331 if ( _url.getScheme() == "dvd" )
332 filesystems.push_back("udf");
334 filesystems.push_back("iso9660");
336 // try all devices in sequence
337 for (DeviceList::iterator it = _devices.begin()
338 ; !mountsucceeded && it != _devices.end()
341 DBG << "count " << count << endl;
342 if (next && count <=_lastdev_tried )
344 DBG << "skipping device " << it->name << endl;
348 _lastdev_tried = count;
350 MediaSource temp( *it);
352 PathInfo dinfo(temp.name);
355 temp.maj_nr = dinfo.major();
356 temp.min_nr = dinfo.minor();
358 DeviceList::const_iterator d( detected.begin());
359 for( ; d != detected.end(); ++d)
361 if( temp.equals( *d))
370 DBG << "skipping invalid device: " << it->name << endl;
374 MediaSourceRef media( new MediaSource(temp));
375 AttachedMedia ret( findAttachedMedia( media));
377 if( ret.mediaSource && ret.attachPoint &&
378 !ret.attachPoint->empty())
380 DBG << "Using a shared media "
381 << ret.mediaSource->name
383 << ret.attachPoint->path
386 setAttachPoint(ret.attachPoint);
387 setMediaSource(ret.mediaSource);
389 mountsucceeded = true;
394 MediaManager manager;
395 MountEntries entries( manager.getMountEntries());
396 MountEntries::const_iterator e;
397 for( e = entries.begin(); e != entries.end(); ++e)
399 bool is_device = false;
400 std::string dev_path(Pathname(e->src).asString());
403 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
404 dev_info(e->src) && dev_info.isBlk())
409 if( is_device && media->maj_nr == dev_info.major() &&
410 media->min_nr == dev_info.minor())
412 AttachPointRef ap( new AttachPoint(e->dir, false));
413 AttachedMedia am( media, ap);
415 DBG << "Using a system mounted media "
421 media->iown = false; // mark attachment as foreign
423 setMediaSource(media);
426 mountsucceeded = true;
436 closeTray( it->name );
438 // try all filesystems in sequence
439 for(list<string>::iterator fsit = filesystems.begin()
440 ; !mountsucceeded && fsit != filesystems.end()
445 if( !isUseableAttachPoint(Pathname(mountpoint)))
447 mountpoint = createAttachPoint().asString();
448 setAttachPoint( mountpoint, true);
449 if( mountpoint.empty())
451 ZYPP_THROW( MediaBadAttachPointException(url()));
455 mount.mount(it->name, mountpoint, *fsit, options);
457 setMediaSource(media);
459 // wait for /etc/mtab update ...
460 // (shouldn't be needed)
462 while( !(mountsucceeded=isAttached()) && --limit)
473 setMediaSource(MediaSourceRef());
476 mount.umount(attachPoint().asString());
478 catch (const MediaException & excpt_r)
480 ZYPP_CAUGHT(excpt_r);
482 ZYPP_THROW(MediaMountException(
483 "Unable to verify that the media was mounted",
488 catch (const MediaMountException &e)
494 catch (const MediaException & excpt_r)
497 ZYPP_CAUGHT(excpt_r);
506 if( !merr.mountOutput().empty())
508 ZYPP_THROW(MediaMountException(merr.mountError(),
511 merr.mountOutput()));
515 ZYPP_THROW(MediaMountException("Mounting media failed",
516 _url.asString(), mountpoint));
519 DBG << _lastdev << " " << count << endl;
523 ///////////////////////////////////////////////////////////////////
526 // METHOD NAME : MediaCD::releaseFrom
527 // METHOD TYPE : PMError
529 // DESCRIPTION : Asserted that media is attached.
531 void MediaCD::releaseFrom( const std::string & ejectDev )
536 AttachedMedia am( attachedMedia());
537 if(am.mediaSource && am.mediaSource->iown)
538 mount.umount(am.attachPoint->path.asString());
540 catch (const Exception & excpt_r)
542 ZYPP_CAUGHT(excpt_r);
543 if (!ejectDev.empty())
545 forceRelaseAllMedia(false);
546 if(openTray( ejectDev ))
549 ZYPP_RETHROW(excpt_r);
553 if (!ejectDev.empty())
555 forceRelaseAllMedia(false);
556 if( !openTray( ejectDev ))
558 #if REPORT_EJECT_ERRORS
559 ZYPP_THROW(MediaNotEjectedException(ejectDev));
565 ///////////////////////////////////////////////////////////////////
568 // METHOD NAME : MediaCD::forceEject
569 // METHOD TYPE : void
571 // Asserted that media is not attached.
573 void MediaCD::forceEject(const std::string & ejectDev)
576 if ( !isAttached()) { // no device mounted in this instance
579 detectDevices(_url.getScheme() == "dvd" ? true : false));
583 DBG << "creating on-demand device list" << endl;
584 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
585 string device( "/dev/cdrom" );
586 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
590 PathInfo dinfo(device);
593 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
595 DeviceList::const_iterator d( detected.begin());
596 for( ; d != detected.end(); ++d)
598 // /dev/cdrom or /dev/dvd to the front
599 if( media.equals( *d))
600 _devices.push_front( *d);
602 _devices.push_back( *d);
607 // no /dev/cdrom or /dev/dvd link
612 DeviceList::iterator it;
613 for( it = _devices.begin(); it != _devices.end(); ++it ) {
614 MediaSourceRef media( new MediaSource( *it));
615 if (media->name != ejectDev)
619 PathInfo dinfo(media->name);
622 media->maj_nr = dinfo.major();
623 media->min_nr = dinfo.minor();
625 DeviceList::const_iterator d( detected.begin());
626 for( ; d != detected.end(); ++d)
628 if( media->equals( *d))
637 DBG << "skipping invalid device: " << it->name << endl;
641 // FIXME: we have also to check if it is mounted in the system
642 AttachedMedia ret( findAttachedMedia( media));
643 if( !ret.mediaSource)
645 forceRelaseAllMedia(media, false);
646 if ( openTray( it->name ) )
649 break; // on 1st success
656 #if REPORT_EJECT_ERRORS
657 ZYPP_THROW(MediaNotEjectedException());
662 ///////////////////////////////////////////////////////////////////
664 // METHOD NAME : MediaCD::isAttached
665 // METHOD TYPE : bool
667 // DESCRIPTION : Override check if media is attached.
670 MediaCD::isAttached() const
672 return checkAttached(false);
675 ///////////////////////////////////////////////////////////////////
677 // METHOD NAME : MediaCD::getFile
678 // METHOD TYPE : PMError
680 // DESCRIPTION : Asserted that media is attached.
682 void MediaCD::getFile( const Pathname & filename ) const
684 MediaHandler::getFile( filename );
687 ///////////////////////////////////////////////////////////////////
689 // METHOD NAME : MediaCD::getDir
690 // METHOD TYPE : PMError
692 // DESCRIPTION : Asserted that media is attached.
694 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
696 MediaHandler::getDir( dirname, recurse_r );
699 ///////////////////////////////////////////////////////////////////
702 // METHOD NAME : MediaCD::getDirInfo
703 // METHOD TYPE : PMError
705 // DESCRIPTION : Asserted that media is attached and retlist is empty.
707 void MediaCD::getDirInfo( std::list<std::string> & retlist,
708 const Pathname & dirname, bool dots ) const
710 MediaHandler::getDirInfo( retlist, dirname, dots );
713 ///////////////////////////////////////////////////////////////////
716 // METHOD NAME : MediaCD::getDirInfo
717 // METHOD TYPE : PMError
719 // DESCRIPTION : Asserted that media is attached and retlist is empty.
721 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
722 const Pathname & dirname, bool dots ) const
724 MediaHandler::getDirInfo( retlist, dirname, dots );
727 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
729 return MediaHandler::getDoesFileExist( filename );
732 bool MediaCD::hasMoreDevices()
734 if (_devices.size() == 0)
736 else if (_lastdev_tried < 0)
739 return (unsigned) _lastdev_tried < _devices.size() - 1;
743 MediaCD::getDetectedDevices(std::vector<std::string> & devices,
744 unsigned int & index) const
747 if (!devices.empty())
750 for (DeviceList::const_iterator it = _devices.begin();
751 it != _devices.end(); ++it)
752 devices.push_back(it->name);
757 // try to detect again if _devices are empty (maybe this method will be
758 // called before _devices get actually filled)
761 DBG << "no device list so far, trying to detect" << endl;
764 detectDevices(_url.getScheme() == "dvd" ? true : false));
766 for (DeviceList::const_iterator it = detected.begin();
767 it != detected.end(); ++it)
768 devices.push_back(it->name);
770 // don't know which one is in use in this case
774 MIL << "got " << devices.size() << " detected devices, current: "
775 << (index < devices.size() ? devices[index] : "<none>")
776 << "(" << index << ")" << endl;
782 // vim: set ts=8 sts=2 sw=2 ai noet: