bbc3d3c162166ef812d9850840b585f1fe106b97
[platform/upstream/libzypp.git] / zypp / media / MediaCD.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCD.cc
10  *
11 */
12 extern "C"
13 {
14 #include <sys/ioctl.h>
15 #include <linux/cdrom.h>
16 #if HAVE_UDEV
17 #include <libudev.h>
18 #endif
19 }
20
21 #ifndef HAVE_UDEV
22 #if HAVE_HAL
23 #include "zypp/target/hal/HalContext.h"
24 #endif
25 #endif
26
27 #include <cstring> // strerror
28 #include <cstdlib> // getenv
29 #include <iostream>
30
31 #include "zypp/base/Logger.h"
32 #include "zypp/ExternalProgram.h"
33 #include "zypp/media/Mount.h"
34 #include "zypp/media/MediaCD.h"
35 #include "zypp/media/MediaManager.h"
36 #include "zypp/Url.h"
37 #include "zypp/AutoDispose.h"
38
39
40
41 /*
42 ** if to throw exception on eject errors or ignore them
43 */
44 #define  REPORT_EJECT_ERRORS     0
45
46 /*
47 ** If defined to the full path of the eject utility,
48 ** it will be used additionally to the eject-ioctl.
49 */
50 #define EJECT_TOOL_PATH "/bin/eject"
51
52
53 using namespace std;
54
55 //////////////////////////////////////////////////////////////////
56 namespace zypp
57 {
58   //////////////////////////////////////////////////////////////////
59   namespace media
60   {
61
62     //////////////////////////////////////////////////////////////////
63     namespace
64     {
65       typedef std::list<MediaSource> DeviceList;
66
67       //////////////////////////////////////////////////////////////////
68       /// \brief Try to detect cd/dvd devices using hal/udev
69       ///
70       /// Returns an empty device list on error.
71       ///
72       /// \todo I took the code more or less as it was from MediaCD::detectDevices
73       /// into this function. Semantic between HAL and UDEV seems to be slightly
74       /// different, esp. in supportingDVD mode. This should be investigated and fixed.
75       //////////////////////////////////////////////////////////////////
76       DeviceList systemDetectDevices( bool supportingDVD_r )
77       {
78         DeviceList detected;
79
80 #ifdef HAVE_UDEV
81         // http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/index.html
82         zypp::AutoDispose<struct udev *> udev( ::udev_new(), ::udev_unref );
83         if ( ! udev )
84         {
85           ERR << "Can't create udev context." << endl;
86           return DeviceList();
87         }
88
89         zypp::AutoDispose<struct udev_enumerate *> enumerate( ::udev_enumerate_new(udev), ::udev_enumerate_unref );
90         if ( ! enumerate )
91         {
92           ERR << "Can't create udev list entry." << endl;
93           return DeviceList();
94         }
95
96         ::udev_enumerate_add_match_subsystem( enumerate, "block" );
97         ::udev_enumerate_add_match_property( enumerate, "ID_CDROM", "1" );
98         ::udev_enumerate_scan_devices( enumerate );
99
100         struct udev_list_entry * entry = 0;
101         udev_list_entry_foreach( entry, ::udev_enumerate_get_list_entry( enumerate ) )
102         {
103           zypp::AutoDispose<struct udev_device *> device( ::udev_device_new_from_syspath( ::udev_enumerate_get_udev( enumerate ),
104                                                                                           ::udev_list_entry_get_name( entry ) ),
105                                                           ::udev_device_unref );
106           if ( ! device )
107           {
108             ERR << "Can't create udev device." << endl;
109             continue;
110           }
111
112           if ( supportingDVD_r && ! ::udev_device_get_property_value( device, "ID_CDROM_DVD" ) )
113           {
114             continue;   // looking for dvd only
115           }
116
117           const char * devnodePtr( ::udev_device_get_devnode( device ) );
118           if ( ! devnodePtr )
119           {
120             ERR << "Got NULL devicenode." << endl;
121             continue;
122           }
123
124           // In case we need it someday:
125           //const char * mountpath = ::udev_device_get_property_value( device, "FSTAB_DIR" );
126
127           PathInfo devnode( devnodePtr );
128           if ( devnode.isBlk() )
129           {
130             MediaSource media( "cdrom", devnode.path().asString(), devnode.devMajor(), devnode.devMinor() );
131             DBG << "Found (udev): " << media << std::endl;
132             detected.push_back( media );
133           }
134         }
135         if ( detected.empty() )
136         {
137           WAR << "Did not find any CD/DVD device." << endl;
138         }
139 #elif HAVE_HAL
140         using namespace zypp::target::hal;
141         try
142         {
143           HalContext hal(true);
144
145           std::vector<std::string> drv_udis;
146           drv_udis = hal.findDevicesByCapability("storage.cdrom");
147
148           DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
149           for(size_t d = 0; d < drv_udis.size(); d++)
150           {
151             HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
152
153             if( drv)
154             {
155               bool supportsDVD=false;
156               if( supportingDVD_r)
157               {
158                 std::vector<std::string> caps;
159                 try {
160                   caps = drv.getCdromCapabilityNames();
161                 }
162                 catch(const HalException &e)
163                 {
164                   ZYPP_CAUGHT(e);
165                 }
166
167                 std::vector<std::string>::const_iterator ci;
168                 for( ci=caps.begin(); ci != caps.end(); ++ci)
169                 {
170                   if( *ci == "dvd")
171                     supportsDVD = true;
172                 }
173               }
174
175               MediaSource media("cdrom", drv.getDeviceFile(),
176                       drv.getDeviceMajor(),
177                       drv.getDeviceMinor());
178                   DBG << "Found " << drv_udis[d] << ": "
179                   << media.asString() << std::endl;
180                   if( supportingDVD_r && supportsDVD)
181                   {
182                     detected.push_front(media);
183                   }
184                   else
185                   {
186                     detected.push_back(media);
187                   }
188             }
189           }
190         }
191         catch(const zypp::target::hal::HalException &e)
192         {
193           ZYPP_CAUGHT(e);
194         }
195 #endif
196         return detected;
197       }
198
199     } // namespace
200     //////////////////////////////////////////////////////////////////
201
202
203   MediaCD::MediaCD( const Url & url_r, const Pathname & attach_point_hint_r )
204     : MediaHandler( url_r, attach_point_hint_r, url_r.getPathName(), false )
205     , _lastdev( -1 )
206     , _lastdev_tried( -1 )
207   {
208     MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")" << endl;
209
210     if ( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd" )
211     {
212       ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
213       ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
214     }
215
216     string devices = _url.getQueryParam( "devices" );
217     if ( ! devices.empty() )
218     {
219       std::vector<std::string> words;
220       str::split( devices, std::back_inserter(words), "," );
221       for ( const std::string & device : words )
222       {
223         if ( device.empty() )
224           continue;
225
226         MediaSource media( "cdrom", device, 0, 0 );
227         _devices.push_back( media );
228         DBG << "use device (delayed verify)" << device << endl;
229       }
230     }
231     else
232     {
233       DBG << "going to use on-demand device list" << endl;
234       return;
235     }
236
237     if ( _devices.empty() )
238     {
239       ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
240       ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
241     }
242   }
243
244   ///////////////////////////////////////////////////////////////////
245   //
246   //
247   //  METHOD NAME : MediaCD::openTray
248   //  METHOD TYPE : bool
249   //
250   bool MediaCD::openTray( const std::string & device_r )
251   {
252     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK|O_CLOEXEC );
253     int res = -1;
254
255     if ( fd != -1)
256     {
257       res = ::ioctl( fd, CDROMEJECT );
258       ::close( fd );
259     }
260
261     if ( res )
262     {
263       if( fd == -1)
264       {
265         WAR << "Unable to open '" << device_r
266             << "' (" << ::strerror( errno ) << ")" << endl;
267       }
268       else
269       {
270         WAR << "Eject " << device_r
271             << " failed (" << ::strerror( errno ) << ")" << endl;
272       }
273
274 #if defined(EJECT_TOOL_PATH)
275       DBG << "Try to eject " << device_r << " using "
276         << EJECT_TOOL_PATH << " utility" << std::endl;
277
278       const char *cmd[3];
279       cmd[0] = EJECT_TOOL_PATH;
280       cmd[1] = device_r.c_str();
281       cmd[2] = NULL;
282       ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
283
284       for(std::string out( eject.receiveLine());
285           out.length(); out = eject.receiveLine())
286       {
287         DBG << " " << out;
288       }
289
290       if(eject.close() != 0)
291       {
292         WAR << "Eject of " << device_r << " failed." << std::endl;
293         return false;
294       }
295 #else
296       return false;
297 #endif
298     }
299     MIL << "Eject of " << device_r << " successful." << endl;
300     return true;
301   }
302
303   ///////////////////////////////////////////////////////////////////
304   //
305   //
306   //    METHOD NAME : MediaCD::closeTray
307   //    METHOD TYPE : bool
308   //
309   bool MediaCD::closeTray( const std::string & device_r )
310   {
311     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK|O_CLOEXEC );
312     if ( fd == -1 ) {
313       WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
314       return false;
315     }
316     int res = ::ioctl( fd, CDROMCLOSETRAY );
317     ::close( fd );
318     if ( res ) {
319       WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
320       return false;
321     }
322     DBG << "Close tray " << device_r << endl;
323     return true;
324   }
325
326
327   MediaCD::DeviceList MediaCD::detectDevices( bool supportingDVD_r ) const
328   {
329     DeviceList detected( systemDetectDevices( supportingDVD_r ) );
330
331     if ( detected.empty() )
332     {
333       WAR << "CD/DVD drive detection with HAL/UDEV failed! Guessing..." << std::endl;
334       PathInfo dvdinfo( "/dev/dvd" );
335       PathInfo cdrinfo( "/dev/cdrom" );
336       if ( dvdinfo.isBlk() )
337       {
338         MediaSource media( "cdrom", dvdinfo.path().asString(), dvdinfo.devMajor(), dvdinfo.devMinor() );
339         DBG << "Found (GUESS): " << media << std::endl;
340         detected.push_back( media );
341       }
342       if ( cdrinfo.isBlk()
343         && ! ( cdrinfo.devMajor() == dvdinfo.devMajor() && cdrinfo.devMinor() == dvdinfo.devMinor() ) )
344       {
345         MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.devMajor(), cdrinfo.devMinor() );
346         DBG << "Found (GUESS): " << media << std::endl;
347         detected.push_back( media );
348       }
349     }
350
351     // NOTE: On the fly build on-demand device list. Code was moved to
352     // here to get rid of code duplication, while keeping the ABI. Acuallty
353     // this code should be moved to a _devices accessor method.
354     if ( _devices.empty() )
355     {
356       DBG << "creating on-demand device list" << endl;
357       //default is /dev/cdrom; for dvd: /dev/dvd if it exists
358       string device( "/dev/cdrom" );
359       if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
360       {
361         device = "/dev/dvd";
362       }
363
364       PathInfo dinfo( device );
365       if ( dinfo.isBlk() )
366       {
367         MediaSource media( "cdrom", device, dinfo.devMajor(), dinfo.devMinor() );
368         if ( detected.empty() )
369         {
370           _devices.push_front( media ); // better try this than nothing
371         }
372         else
373         {
374           for( const auto & d : detected )
375           {
376             // /dev/cdrom or /dev/dvd to the front
377             if ( media.equals( d ) )
378               _devices.push_front( d );
379             else
380               _devices.push_back( d );
381           }
382         }
383       }
384       else
385       {
386         // no /dev/cdrom or /dev/dvd link
387         _devices = detected;
388       }
389     }
390
391     return detected;
392   }
393
394
395   ///////////////////////////////////////////////////////////////////
396   //
397   //
398   //  METHOD NAME : MediaCD::attachTo
399   //  METHOD TYPE : PMError
400   //
401   //  DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
402   //
403   void MediaCD::attachTo( bool next )
404   {
405     DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
406     if ( next && _lastdev == -1 )
407       ZYPP_THROW(MediaNotSupportedException(url()));
408
409     // This also fills the _devices list on demand
410     DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
411
412     Mount mount;
413     MediaMountException merr;
414     string mountpoint = attachPoint().asString();
415
416     string options = _url.getQueryParam( "mountoptions" );
417     if ( options.empty() )
418     {
419       options="ro";
420     }
421
422     //TODO: make configurable
423     list<string> filesystems;
424
425     // if DVD, try UDF filesystem before iso9660
426     if ( _url.getScheme() == "dvd" )
427       filesystems.push_back("udf");
428
429     filesystems.push_back("iso9660");
430
431     // try all devices in sequence
432     int count = 0;
433     bool mountsucceeded = false;
434     for ( DeviceList::iterator it = _devices.begin() ; ! mountsucceeded && it != _devices.end() ; ++it, ++count )
435     {
436       DBG << "count " << count << endl;
437       if (next && count <=_lastdev_tried )
438       {
439         DBG << "skipping device " << it->name << endl;
440         continue;
441       }
442       _lastdev_tried = count;
443
444       // bnc#755815: _devices contains either devices passed as url option
445       //        or autodetected ones. Accept both as long as they are block
446       //        devices.
447       MediaSource temp( *it );
448       PathInfo dinfo( temp.name );
449       if ( ! dinfo.isBlk() )
450       {
451         WAR <<  "skipping non block device: " << dinfo << endl;
452         continue;
453       }
454       DBG << "trying device " << dinfo << endl;
455
456       temp.maj_nr = dinfo.devMajor();
457       temp.min_nr = dinfo.devMinor();
458       MediaSourceRef media( new MediaSource(temp));
459       AttachedMedia ret( findAttachedMedia( media));
460
461       if( ret.mediaSource && ret.attachPoint &&
462          !ret.attachPoint->empty())
463       {
464         DBG << "Using a shared media "
465             << ret.mediaSource->name
466             << " attached on "
467             << ret.attachPoint->path
468             << endl;
469         removeAttachPoint();
470         setAttachPoint(ret.attachPoint);
471         setMediaSource(ret.mediaSource);
472         _lastdev = count;
473         mountsucceeded = true;
474         break;
475       }
476
477       {
478         MediaManager  manager;
479         MountEntries  entries( manager.getMountEntries());
480         MountEntries::const_iterator e;
481         for( e = entries.begin(); e != entries.end(); ++e)
482         {
483           bool        is_device = false;
484           std::string dev_path(Pathname(e->src).asString());
485           PathInfo    dev_info;
486
487           if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
488               dev_info(e->src) && dev_info.isBlk())
489           {
490             is_device = true;
491           }
492
493           if( is_device && media->maj_nr == dev_info.devMajor() &&
494                            media->min_nr == dev_info.devMinor())
495           {
496             AttachPointRef ap( new AttachPoint(e->dir, false));
497             AttachedMedia  am( media, ap);
498             {
499               DBG << "Using a system mounted media "
500                   << media->name
501                   << " attached on "
502                   << ap->path
503                   << endl;
504
505               media->iown = false; // mark attachment as foreign
506
507               setMediaSource(media);
508               setAttachPoint(ap);
509               _lastdev = count;
510               mountsucceeded = true;
511               break;
512             }
513           }
514         }
515         if( mountsucceeded)
516           break;
517       }
518
519       // close tray
520       closeTray( it->name );
521
522       // try all filesystems in sequence
523       for(list<string>::iterator fsit = filesystems.begin()
524           ; !mountsucceeded && fsit != filesystems.end()
525           ; ++fsit)
526       {
527         try
528         {
529           if( !isUseableAttachPoint(Pathname(mountpoint)))
530           {
531             mountpoint = createAttachPoint().asString();
532             setAttachPoint( mountpoint, true);
533             if( mountpoint.empty())
534             {
535               ZYPP_THROW( MediaBadAttachPointException(url()));
536             }
537           }
538
539           mount.mount(it->name, mountpoint, *fsit, options);
540
541           setMediaSource(media);
542
543           // wait for /etc/mtab update ...
544           // (shouldn't be needed)
545           int limit = 2;
546           while( !(mountsucceeded=isAttached()) && --limit)
547           {
548             WAR << "Wait for /proc/mounts update and retry...." << endl;
549             sleep(1);
550           }
551
552           if( mountsucceeded)
553           {
554             _lastdev = count;
555           }
556           else
557           {
558             setMediaSource(MediaSourceRef());
559             try
560             {
561               mount.umount(attachPoint().asString());
562             }
563             catch (const MediaException & excpt_r)
564             {
565               ZYPP_CAUGHT(excpt_r);
566             }
567             ZYPP_THROW(MediaMountException(
568               "Unable to verify that the media was mounted",
569               it->name, mountpoint
570             ));
571           }
572         }
573         catch (const MediaMountException &e)
574         {
575           merr = e;
576           removeAttachPoint();
577           ZYPP_CAUGHT(e);
578         }
579         catch (const MediaException & excpt_r)
580         {
581           removeAttachPoint();
582           ZYPP_CAUGHT(excpt_r);
583         }
584       } // for filesystems
585     } // for _devices
586
587     if (!mountsucceeded)
588     {
589       _lastdev = -1;
590
591       if( !merr.mountOutput().empty())
592       {
593         ZYPP_THROW(MediaMountException(merr.mountError(),
594                                        _url.asString(),
595                                        mountpoint,
596                                        merr.mountOutput()));
597       }
598       else
599       {
600         ZYPP_THROW(MediaMountException("Mounting media failed",
601                                        _url.asString(), mountpoint));
602       }
603     }
604     DBG << _lastdev << " " << count << endl;
605   }
606
607
608   ///////////////////////////////////////////////////////////////////
609   //
610   //
611   //  METHOD NAME : MediaCD::releaseFrom
612   //  METHOD TYPE : PMError
613   //
614   //  DESCRIPTION : Asserted that media is attached.
615   //
616   void MediaCD::releaseFrom( const std::string & ejectDev )
617   {
618     Mount mount;
619     try
620     {
621       AttachedMedia am( attachedMedia());
622       if(am.mediaSource && am.mediaSource->iown)
623         mount.umount(am.attachPoint->path.asString());
624     }
625     catch (const Exception & excpt_r)
626     {
627       ZYPP_CAUGHT(excpt_r);
628       if (!ejectDev.empty())
629       {
630         forceRelaseAllMedia(false);
631         if(openTray( ejectDev ))
632           return;
633       }
634       ZYPP_RETHROW(excpt_r);
635     }
636
637     // eject device
638     if (!ejectDev.empty())
639     {
640       forceRelaseAllMedia(false);
641       if( !openTray( ejectDev ))
642       {
643 #if REPORT_EJECT_ERRORS
644         ZYPP_THROW(MediaNotEjectedException(ejectDev));
645 #endif
646       }
647     }
648   }
649
650   ///////////////////////////////////////////////////////////////////
651   //
652   //
653   //  METHOD NAME : MediaCD::forceEject
654   //  METHOD TYPE : void
655   //
656   // Asserted that media is not attached.
657   //
658   void MediaCD::forceEject( const std::string & ejectDev_r )
659   {
660 #if REPORT_EJECT_ERRORS
661     bool ejected = false;
662 #endif
663     if ( ! isAttached() )       // no device mounted in this instance
664     {
665       // This also fills the _devices list on demand
666       DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
667       for_( it, _devices.begin(), _devices.end() )
668       {
669         MediaSourceRef media( new MediaSource( *it ) );
670         if ( media->name != ejectDev_r )
671           continue;
672
673         // bnc#755815: _devices contains either devices passed as url option
674         //      or autodetected ones. Accept both as long as they are block
675         //      devices.
676         PathInfo dinfo( media->name );
677         if( ! dinfo.isBlk() )
678         {
679           WAR <<  "skipping non block device: " << dinfo << endl;
680           continue;
681         }
682         DBG << "trying device " << dinfo << endl;
683
684         // FIXME: we have also to check if it is mounted in the system
685         AttachedMedia ret( findAttachedMedia( media));
686         if( !ret.mediaSource )
687         {
688           forceRelaseAllMedia( media, false );
689           if ( openTray( it->name ) )
690           {
691 #if REPORT_EJECT_ERRORS
692             ejected = true;
693 #endif
694             break; // on 1st success
695           }
696         }
697       }
698     }
699 #if REPORT_EJECT_ERRORS
700     if( !ejected)
701     {
702       ZYPP_THROW(MediaNotEjectedException());
703     }
704 #endif
705   }
706
707   ///////////////////////////////////////////////////////////////////
708   //
709   //  METHOD NAME : MediaCD::isAttached
710   //  METHOD TYPE : bool
711   //
712   //  DESCRIPTION : Override check if media is attached.
713   //
714   bool
715   MediaCD::isAttached() const
716   {
717     return checkAttached(false);
718   }
719
720   ///////////////////////////////////////////////////////////////////
721   //
722   //  METHOD NAME : MediaCD::getFile
723   //  METHOD TYPE : PMError
724   //
725   //  DESCRIPTION : Asserted that media is attached.
726   //
727   void MediaCD::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
728   {
729     MediaHandler::getFile( filename, expectedFileSize_r );
730   }
731
732   ///////////////////////////////////////////////////////////////////
733   //
734   //  METHOD NAME : MediaCD::getDir
735   //  METHOD TYPE : PMError
736   //
737   //  DESCRIPTION : Asserted that media is attached.
738   //
739   void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
740   {
741     MediaHandler::getDir( dirname, recurse_r );
742   }
743
744   ///////////////////////////////////////////////////////////////////
745   //
746   //
747   //  METHOD NAME : MediaCD::getDirInfo
748   //  METHOD TYPE : PMError
749   //
750   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
751   //
752   void MediaCD::getDirInfo( std::list<std::string> & retlist,
753                             const Pathname & dirname, bool dots ) const
754   {
755     MediaHandler::getDirInfo( retlist, dirname, dots );
756   }
757
758   ///////////////////////////////////////////////////////////////////
759   //
760   //
761   //  METHOD NAME : MediaCD::getDirInfo
762   //  METHOD TYPE : PMError
763   //
764   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
765   //
766   void MediaCD::getDirInfo( filesystem::DirContent & retlist, const Pathname & dirname, bool dots ) const
767   {
768     MediaHandler::getDirInfo( retlist, dirname, dots );
769   }
770
771
772   bool MediaCD::getDoesFileExist( const Pathname & filename ) const
773   {
774     return MediaHandler::getDoesFileExist( filename );
775   }
776
777
778   bool MediaCD::hasMoreDevices()
779   {
780     if (_devices.size() == 0)
781       return false;
782     else if (_lastdev_tried < 0)
783       return true;
784
785     return (unsigned) _lastdev_tried < _devices.size() - 1;
786   }
787
788
789   void MediaCD::getDetectedDevices( std::vector<std::string> & devices, unsigned int & index ) const
790   {
791     if ( ! devices.empty() )
792       devices.clear();
793
794     if ( _devices.empty() )
795       // This also fills the _devices list on demand
796       detectDevices( _url.getScheme() == "dvd" ? true : false );
797
798     for ( const auto & it : _devices )
799       devices.push_back( it.name );
800
801     index = ( _lastdev >= 0  ? (unsigned)_lastdev : 0 );
802
803     MIL << "got " << devices.size() << " detected devices, current: "
804         << (index < devices.size() ? devices[index] : "<none>")
805         << "(" << index << ")" << endl;
806   }
807
808   } // namespace media
809   //////////////////////////////////////////////////////////////////
810 } // namespace zypp
811 //////////////////////////////////////////////////////////////////