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 zypp::AutoDispose<struct udev *> udev( ::udev_new(), ::udev_unref );
208 ERR << "Can't create udev context." << endl;
212 zypp::AutoDispose<struct udev_enumerate *> enumerate( ::udev_enumerate_new(udev), ::udev_enumerate_unref );
215 ERR << "Can't create udev list entry." << endl;
219 ::udev_enumerate_add_match_subsystem( enumerate, "block" );
220 ::udev_enumerate_add_match_property( enumerate, "ID_CDROM", "1" );
221 ::udev_enumerate_scan_devices( enumerate );
224 struct udev_list_entry * entry = 0;
225 udev_list_entry_foreach( entry, ::udev_enumerate_get_list_entry( enumerate ) )
227 zypp::AutoDispose<struct udev_device *> device( ::udev_device_new_from_syspath( ::udev_enumerate_get_udev( enumerate ),
228 ::udev_list_entry_get_name( entry ) ),
229 ::udev_device_unref );
232 ERR << "Can't create udev device." << endl;
236 if ( supportingDVD && ! ::udev_device_get_property_value( device, "ID_CDROM_DVD" ) )
238 continue; // looking for dvd only
241 const char * devnodePtr( ::udev_device_get_devnode( device ) );
244 ERR << "Got NULL devicenode." << endl;
248 // In case we need it someday:
249 //const char * mountpath = ::udev_device_get_property_value( device, "FSTAB_DIR" );
251 PathInfo devnode( devnodePtr );
252 if ( devnode.isBlk() )
254 MediaSource media( "cdrom", devnode.path().asString(), devnode.major(), devnode.minor() );
255 DBG << "Found (udev): " << media << std::endl;
256 detected.push_back( media );
263 ///////////////////////////////////////////////////////////////////
266 // METHOD NAME : MediaCD::attachTo
267 // METHOD TYPE : PMError
269 // DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
271 void MediaCD::attachTo(bool next)
273 DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
274 if (next && _lastdev == -1)
275 ZYPP_THROW(MediaNotSupportedException(url()));
278 detectDevices(_url.getScheme() == "dvd" ? true : false));
282 DBG << "creating on-demand device list" << endl;
283 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
284 string device( "/dev/cdrom" );
285 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
290 PathInfo dinfo(device);
293 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
295 DeviceList::const_iterator d( detected.begin());
296 for( ; d != detected.end(); ++d)
298 // /dev/cdrom or /dev/dvd to the front
299 if( media.equals( *d))
300 _devices.push_front( *d);
302 _devices.push_back( *d);
307 // no /dev/cdrom or /dev/dvd link
313 string mountpoint = attachPoint().asString();
314 bool mountsucceeded = false;
316 MediaMountException merr;
318 string options = _url.getQueryParam("mountoptions");
324 //TODO: make configurable
325 list<string> filesystems;
327 // if DVD, try UDF filesystem before iso9660
328 if ( _url.getScheme() == "dvd" )
329 filesystems.push_back("udf");
331 filesystems.push_back("iso9660");
333 // try all devices in sequence
334 for (DeviceList::iterator it = _devices.begin()
335 ; !mountsucceeded && it != _devices.end()
338 DBG << "count " << count << endl;
339 if (next && count <=_lastdev_tried )
341 DBG << "skipping device " << it->name << endl;
345 _lastdev_tried = count;
347 MediaSource temp( *it);
349 PathInfo dinfo(temp.name);
352 temp.maj_nr = dinfo.major();
353 temp.min_nr = dinfo.minor();
355 DeviceList::const_iterator d( detected.begin());
356 for( ; d != detected.end(); ++d)
358 if( temp.equals( *d))
367 DBG << "skipping invalid device: " << it->name << endl;
371 MediaSourceRef media( new MediaSource(temp));
372 AttachedMedia ret( findAttachedMedia( media));
374 if( ret.mediaSource && ret.attachPoint &&
375 !ret.attachPoint->empty())
377 DBG << "Using a shared media "
378 << ret.mediaSource->name
380 << ret.attachPoint->path
383 setAttachPoint(ret.attachPoint);
384 setMediaSource(ret.mediaSource);
386 mountsucceeded = true;
391 MediaManager manager;
392 MountEntries entries( manager.getMountEntries());
393 MountEntries::const_iterator e;
394 for( e = entries.begin(); e != entries.end(); ++e)
396 bool is_device = false;
397 std::string dev_path(Pathname(e->src).asString());
400 if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
401 dev_info(e->src) && dev_info.isBlk())
406 if( is_device && media->maj_nr == dev_info.major() &&
407 media->min_nr == dev_info.minor())
409 AttachPointRef ap( new AttachPoint(e->dir, false));
410 AttachedMedia am( media, ap);
412 DBG << "Using a system mounted media "
418 media->iown = false; // mark attachment as foreign
420 setMediaSource(media);
423 mountsucceeded = true;
433 closeTray( it->name );
435 // try all filesystems in sequence
436 for(list<string>::iterator fsit = filesystems.begin()
437 ; !mountsucceeded && fsit != filesystems.end()
442 if( !isUseableAttachPoint(Pathname(mountpoint)))
444 mountpoint = createAttachPoint().asString();
445 setAttachPoint( mountpoint, true);
446 if( mountpoint.empty())
448 ZYPP_THROW( MediaBadAttachPointException(url()));
452 mount.mount(it->name, mountpoint, *fsit, options);
454 setMediaSource(media);
456 // wait for /etc/mtab update ...
457 // (shouldn't be needed)
459 while( !(mountsucceeded=isAttached()) && --limit)
470 setMediaSource(MediaSourceRef());
473 mount.umount(attachPoint().asString());
475 catch (const MediaException & excpt_r)
477 ZYPP_CAUGHT(excpt_r);
479 ZYPP_THROW(MediaMountException(
480 "Unable to verify that the media was mounted",
485 catch (const MediaMountException &e)
491 catch (const MediaException & excpt_r)
494 ZYPP_CAUGHT(excpt_r);
503 if( !merr.mountOutput().empty())
505 ZYPP_THROW(MediaMountException(merr.mountError(),
508 merr.mountOutput()));
512 ZYPP_THROW(MediaMountException("Mounting media failed",
513 _url.asString(), mountpoint));
516 DBG << _lastdev << " " << count << endl;
520 ///////////////////////////////////////////////////////////////////
523 // METHOD NAME : MediaCD::releaseFrom
524 // METHOD TYPE : PMError
526 // DESCRIPTION : Asserted that media is attached.
528 void MediaCD::releaseFrom( const std::string & ejectDev )
533 AttachedMedia am( attachedMedia());
534 if(am.mediaSource && am.mediaSource->iown)
535 mount.umount(am.attachPoint->path.asString());
537 catch (const Exception & excpt_r)
539 ZYPP_CAUGHT(excpt_r);
540 if (!ejectDev.empty())
542 forceRelaseAllMedia(false);
543 if(openTray( ejectDev ))
546 ZYPP_RETHROW(excpt_r);
550 if (!ejectDev.empty())
552 forceRelaseAllMedia(false);
553 if( !openTray( ejectDev ))
555 #if REPORT_EJECT_ERRORS
556 ZYPP_THROW(MediaNotEjectedException(ejectDev));
562 ///////////////////////////////////////////////////////////////////
565 // METHOD NAME : MediaCD::forceEject
566 // METHOD TYPE : void
568 // Asserted that media is not attached.
570 void MediaCD::forceEject(const std::string & ejectDev)
573 if ( !isAttached()) { // no device mounted in this instance
576 detectDevices(_url.getScheme() == "dvd" ? true : false));
580 DBG << "creating on-demand device list" << endl;
581 //default is /dev/cdrom; for dvd: /dev/dvd if it exists
582 string device( "/dev/cdrom" );
583 if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
587 PathInfo dinfo(device);
590 MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
592 DeviceList::const_iterator d( detected.begin());
593 for( ; d != detected.end(); ++d)
595 // /dev/cdrom or /dev/dvd to the front
596 if( media.equals( *d))
597 _devices.push_front( *d);
599 _devices.push_back( *d);
604 // no /dev/cdrom or /dev/dvd link
609 DeviceList::iterator it;
610 for( it = _devices.begin(); it != _devices.end(); ++it ) {
611 MediaSourceRef media( new MediaSource( *it));
612 if (media->name != ejectDev)
616 PathInfo dinfo(media->name);
619 media->maj_nr = dinfo.major();
620 media->min_nr = dinfo.minor();
622 DeviceList::const_iterator d( detected.begin());
623 for( ; d != detected.end(); ++d)
625 if( media->equals( *d))
634 DBG << "skipping invalid device: " << it->name << endl;
638 // FIXME: we have also to check if it is mounted in the system
639 AttachedMedia ret( findAttachedMedia( media));
640 if( !ret.mediaSource)
642 forceRelaseAllMedia(media, false);
643 if ( openTray( it->name ) )
646 break; // on 1st success
653 #if REPORT_EJECT_ERRORS
654 ZYPP_THROW(MediaNotEjectedException());
659 ///////////////////////////////////////////////////////////////////
661 // METHOD NAME : MediaCD::isAttached
662 // METHOD TYPE : bool
664 // DESCRIPTION : Override check if media is attached.
667 MediaCD::isAttached() const
669 return checkAttached(false);
672 ///////////////////////////////////////////////////////////////////
674 // METHOD NAME : MediaCD::getFile
675 // METHOD TYPE : PMError
677 // DESCRIPTION : Asserted that media is attached.
679 void MediaCD::getFile( const Pathname & filename ) const
681 MediaHandler::getFile( filename );
684 ///////////////////////////////////////////////////////////////////
686 // METHOD NAME : MediaCD::getDir
687 // METHOD TYPE : PMError
689 // DESCRIPTION : Asserted that media is attached.
691 void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
693 MediaHandler::getDir( dirname, recurse_r );
696 ///////////////////////////////////////////////////////////////////
699 // METHOD NAME : MediaCD::getDirInfo
700 // METHOD TYPE : PMError
702 // DESCRIPTION : Asserted that media is attached and retlist is empty.
704 void MediaCD::getDirInfo( std::list<std::string> & retlist,
705 const Pathname & dirname, bool dots ) const
707 MediaHandler::getDirInfo( retlist, dirname, dots );
710 ///////////////////////////////////////////////////////////////////
713 // METHOD NAME : MediaCD::getDirInfo
714 // METHOD TYPE : PMError
716 // DESCRIPTION : Asserted that media is attached and retlist is empty.
718 void MediaCD::getDirInfo( filesystem::DirContent & retlist,
719 const Pathname & dirname, bool dots ) const
721 MediaHandler::getDirInfo( retlist, dirname, dots );
724 bool MediaCD::getDoesFileExist( const Pathname & filename ) const
726 return MediaHandler::getDoesFileExist( filename );
729 bool MediaCD::hasMoreDevices()
731 if (_devices.size() == 0)
733 else if (_lastdev_tried < 0)
736 return (unsigned) _lastdev_tried < _devices.size() - 1;
740 MediaCD::getDetectedDevices(std::vector<std::string> & devices,
741 unsigned int & index) const
744 if (!devices.empty())
747 for (DeviceList::const_iterator it = _devices.begin();
748 it != _devices.end(); ++it)
749 devices.push_back(it->name);
754 // try to detect again if _devices are empty (maybe this method will be
755 // called before _devices get actually filled)
758 DBG << "no device list so far, trying to detect" << endl;
761 detectDevices(_url.getScheme() == "dvd" ? true : false));
763 for (DeviceList::const_iterator it = detected.begin();
764 it != detected.end(); ++it)
765 devices.push_back(it->name);
767 // don't know which one is in use in this case
771 MIL << "got " << devices.size() << " detected devices, current: "
772 << (index < devices.size() ? devices[index] : "<none>")
773 << "(" << index << ")" << endl;
779 // vim: set ts=8 sts=2 sw=2 ai noet: