Imported Upstream version 16.3.2
[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     if( !isUseableAttachPoint( attachPoint() ) )
413     {
414       setAttachPoint( createAttachPoint(), true );
415     }
416     std::string mountpoint( attachPoint().asString() );
417
418     Mount mount;
419     MediaMountException merr;
420
421     string options = _url.getQueryParam( "mountoptions" );
422     if ( options.empty() )
423     {
424       options="ro";
425     }
426
427     //TODO: make configurable
428     list<string> filesystems;
429
430     // if DVD, try UDF filesystem before iso9660
431     if ( _url.getScheme() == "dvd" )
432       filesystems.push_back("udf");
433
434     filesystems.push_back("iso9660");
435
436     // try all devices in sequence
437     int count = 0;
438     bool mountsucceeded = false;
439     for ( DeviceList::iterator it = _devices.begin() ; ! mountsucceeded && it != _devices.end() ; ++it, ++count )
440     {
441       DBG << "count " << count << endl;
442       if (next && count <=_lastdev_tried )
443       {
444         DBG << "skipping device " << it->name << endl;
445         continue;
446       }
447       _lastdev_tried = count;
448
449       // bnc#755815: _devices contains either devices passed as url option
450       //        or autodetected ones. Accept both as long as they are block
451       //        devices.
452       MediaSource temp( *it );
453       PathInfo dinfo( temp.name );
454       if ( ! dinfo.isBlk() )
455       {
456         WAR <<  "skipping non block device: " << dinfo << endl;
457         continue;
458       }
459       DBG << "trying device " << dinfo << endl;
460
461       temp.maj_nr = dinfo.devMajor();
462       temp.min_nr = dinfo.devMinor();
463       MediaSourceRef media( new MediaSource(temp));
464       AttachedMedia ret( findAttachedMedia( media));
465
466       if( ret.mediaSource && ret.attachPoint &&
467          !ret.attachPoint->empty())
468       {
469         DBG << "Using a shared media "
470             << ret.mediaSource->name
471             << " attached on "
472             << ret.attachPoint->path
473             << endl;
474         removeAttachPoint();
475         setAttachPoint(ret.attachPoint);
476         setMediaSource(ret.mediaSource);
477         _lastdev = count;
478         mountsucceeded = true;
479         break;
480       }
481
482       {
483         MediaManager  manager;
484         MountEntries  entries( manager.getMountEntries());
485         MountEntries::const_iterator e;
486         for( e = entries.begin(); e != entries.end(); ++e)
487         {
488           bool        is_device = false;
489           std::string dev_path(Pathname(e->src).asString());
490           PathInfo    dev_info;
491
492           if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
493               dev_info(e->src) && dev_info.isBlk())
494           {
495             is_device = true;
496           }
497
498           if( is_device && media->maj_nr == dev_info.devMajor() &&
499                            media->min_nr == dev_info.devMinor())
500           {
501             AttachPointRef ap( new AttachPoint(e->dir, false));
502             AttachedMedia  am( media, ap);
503             {
504               DBG << "Using a system mounted media "
505                   << media->name
506                   << " attached on "
507                   << ap->path
508                   << endl;
509
510               media->iown = false; // mark attachment as foreign
511
512               setMediaSource(media);
513               setAttachPoint(ap);
514               _lastdev = count;
515               mountsucceeded = true;
516               break;
517             }
518           }
519         }
520         if( mountsucceeded)
521           break;
522       }
523
524       // close tray
525       closeTray( it->name );
526
527       // try all filesystems in sequence
528       for(list<string>::iterator fsit = filesystems.begin()
529           ; !mountsucceeded && fsit != filesystems.end()
530           ; ++fsit)
531       {
532         try
533         {
534           mount.mount(it->name, mountpoint, *fsit, options);
535
536           setMediaSource(media);
537
538           // wait for /etc/mtab update ...
539           // (shouldn't be needed)
540           int limit = 2;
541           while( !(mountsucceeded=isAttached()) && --limit)
542           {
543             WAR << "Wait for /proc/mounts update and retry...." << endl;
544             sleep(1);
545           }
546
547           if( mountsucceeded)
548           {
549             _lastdev = count;
550           }
551           else
552           {
553             setMediaSource(MediaSourceRef());
554             try
555             {
556               mount.umount(attachPoint().asString());
557             }
558             catch (const MediaException & excpt_r)
559             {
560               ZYPP_CAUGHT(excpt_r);
561             }
562             ZYPP_THROW(MediaMountException(
563               "Unable to verify that the media was mounted",
564               it->name, mountpoint
565             ));
566           }
567         }
568         catch (const MediaMountException &e)
569         {
570           merr = e;
571           removeAttachPoint();
572           ZYPP_CAUGHT(e);
573         }
574         catch (const MediaException & excpt_r)
575         {
576           removeAttachPoint();
577           ZYPP_CAUGHT(excpt_r);
578         }
579       } // for filesystems
580     } // for _devices
581
582     if (!mountsucceeded)
583     {
584       _lastdev = -1;
585
586       if( !merr.mountOutput().empty())
587       {
588         ZYPP_THROW(MediaMountException(merr.mountError(),
589                                        _url.asString(),
590                                        mountpoint,
591                                        merr.mountOutput()));
592       }
593       else
594       {
595         ZYPP_THROW(MediaMountException("Mounting media failed",
596                                        _url.asString(), mountpoint));
597       }
598     }
599     DBG << _lastdev << " " << count << endl;
600   }
601
602
603   ///////////////////////////////////////////////////////////////////
604   //
605   //
606   //  METHOD NAME : MediaCD::releaseFrom
607   //  METHOD TYPE : PMError
608   //
609   //  DESCRIPTION : Asserted that media is attached.
610   //
611   void MediaCD::releaseFrom( const std::string & ejectDev )
612   {
613     Mount mount;
614     try
615     {
616       AttachedMedia am( attachedMedia());
617       if(am.mediaSource && am.mediaSource->iown)
618         mount.umount(am.attachPoint->path.asString());
619     }
620     catch (const Exception & excpt_r)
621     {
622       ZYPP_CAUGHT(excpt_r);
623       if (!ejectDev.empty())
624       {
625         forceRelaseAllMedia(false);
626         if(openTray( ejectDev ))
627           return;
628       }
629       ZYPP_RETHROW(excpt_r);
630     }
631
632     // eject device
633     if (!ejectDev.empty())
634     {
635       forceRelaseAllMedia(false);
636       if( !openTray( ejectDev ))
637       {
638 #if REPORT_EJECT_ERRORS
639         ZYPP_THROW(MediaNotEjectedException(ejectDev));
640 #endif
641       }
642     }
643   }
644
645   ///////////////////////////////////////////////////////////////////
646   //
647   //
648   //  METHOD NAME : MediaCD::forceEject
649   //  METHOD TYPE : void
650   //
651   // Asserted that media is not attached.
652   //
653   void MediaCD::forceEject( const std::string & ejectDev_r )
654   {
655 #if REPORT_EJECT_ERRORS
656     bool ejected = false;
657 #endif
658     if ( ! isAttached() )       // no device mounted in this instance
659     {
660       // This also fills the _devices list on demand
661       DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
662       for_( it, _devices.begin(), _devices.end() )
663       {
664         MediaSourceRef media( new MediaSource( *it ) );
665         if ( media->name != ejectDev_r )
666           continue;
667
668         // bnc#755815: _devices contains either devices passed as url option
669         //      or autodetected ones. Accept both as long as they are block
670         //      devices.
671         PathInfo dinfo( media->name );
672         if( ! dinfo.isBlk() )
673         {
674           WAR <<  "skipping non block device: " << dinfo << endl;
675           continue;
676         }
677         DBG << "trying device " << dinfo << endl;
678
679         // FIXME: we have also to check if it is mounted in the system
680         AttachedMedia ret( findAttachedMedia( media));
681         if( !ret.mediaSource )
682         {
683           forceRelaseAllMedia( media, false );
684           if ( openTray( it->name ) )
685           {
686 #if REPORT_EJECT_ERRORS
687             ejected = true;
688 #endif
689             break; // on 1st success
690           }
691         }
692       }
693     }
694 #if REPORT_EJECT_ERRORS
695     if( !ejected)
696     {
697       ZYPP_THROW(MediaNotEjectedException());
698     }
699 #endif
700   }
701
702   ///////////////////////////////////////////////////////////////////
703   //
704   //  METHOD NAME : MediaCD::isAttached
705   //  METHOD TYPE : bool
706   //
707   //  DESCRIPTION : Override check if media is attached.
708   //
709   bool
710   MediaCD::isAttached() const
711   {
712     return checkAttached(false);
713   }
714
715   ///////////////////////////////////////////////////////////////////
716   //
717   //  METHOD NAME : MediaCD::getFile
718   //  METHOD TYPE : PMError
719   //
720   //  DESCRIPTION : Asserted that media is attached.
721   //
722   void MediaCD::getFile( const Pathname & filename ) const
723   {
724     MediaHandler::getFile( filename );
725   }
726
727   ///////////////////////////////////////////////////////////////////
728   //
729   //  METHOD NAME : MediaCD::getDir
730   //  METHOD TYPE : PMError
731   //
732   //  DESCRIPTION : Asserted that media is attached.
733   //
734   void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
735   {
736     MediaHandler::getDir( dirname, recurse_r );
737   }
738
739   ///////////////////////////////////////////////////////////////////
740   //
741   //
742   //  METHOD NAME : MediaCD::getDirInfo
743   //  METHOD TYPE : PMError
744   //
745   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
746   //
747   void MediaCD::getDirInfo( std::list<std::string> & retlist,
748                             const Pathname & dirname, bool dots ) const
749   {
750     MediaHandler::getDirInfo( retlist, dirname, dots );
751   }
752
753   ///////////////////////////////////////////////////////////////////
754   //
755   //
756   //  METHOD NAME : MediaCD::getDirInfo
757   //  METHOD TYPE : PMError
758   //
759   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
760   //
761   void MediaCD::getDirInfo( filesystem::DirContent & retlist, const Pathname & dirname, bool dots ) const
762   {
763     MediaHandler::getDirInfo( retlist, dirname, dots );
764   }
765
766
767   bool MediaCD::getDoesFileExist( const Pathname & filename ) const
768   {
769     return MediaHandler::getDoesFileExist( filename );
770   }
771
772
773   bool MediaCD::hasMoreDevices()
774   {
775     if (_devices.size() == 0)
776       return false;
777     else if (_lastdev_tried < 0)
778       return true;
779
780     return (unsigned) _lastdev_tried < _devices.size() - 1;
781   }
782
783
784   void MediaCD::getDetectedDevices( std::vector<std::string> & devices, unsigned int & index ) const
785   {
786     if ( ! devices.empty() )
787       devices.clear();
788
789     if ( _devices.empty() )
790       // This also fills the _devices list on demand
791       detectDevices( _url.getScheme() == "dvd" ? true : false );
792
793     for ( const auto & it : _devices )
794       devices.push_back( it.name );
795
796     index = ( _lastdev >= 0  ? (unsigned)_lastdev : 0 );
797
798     MIL << "got " << devices.size() << " detected devices, current: "
799         << (index < devices.size() ? devices[index] : "<none>")
800         << "(" << index << ")" << endl;
801   }
802
803   } // namespace media
804   //////////////////////////////////////////////////////////////////
805 } // namespace zypp
806 //////////////////////////////////////////////////////////////////