Remove obsolete DISABLE_AUTOMOUNTER code.
[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
13 #include <iostream>
14
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"
20 #include "zypp/Url.h"
21 #include "zypp/target/hal/HalContext.h"
22
23 #include <cstring> // strerror
24 #include <cstdlib> // getenv
25
26 #include <errno.h>
27 #include <dirent.h>
28
29 #include <sys/ioctl.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <unistd.h> // geteuid, ...
33
34 #include <linux/cdrom.h>
35
36 /*
37 ** try umount of foreign (user/automounter) media on eject
38 **   0 = don't force, 1 = automounted only, 2 == all
39 */
40 #define  FORCE_RELEASE_FOREIGN   2
41
42 /*
43 ** Reuse foreign (user/automounter) mount points.
44 ** 0 = don't use, 1 = automounted only, 2 = all
45 */
46 #define  REUSE_FOREIGN_MOUNTS    2
47
48 /*
49 ** if to throw exception on eject errors or ignore them
50 */
51 #define  REPORT_EJECT_ERRORS     1
52
53 /*
54 ** If defined to the full path of the eject utility,
55 ** it will be used additionally to the eject-ioctl.
56 */
57 #define EJECT_TOOL_PATH "/bin/eject"
58
59
60 using namespace std;
61
62 namespace zypp {
63   namespace media {
64
65 ///////////////////////////////////////////////////////////////////
66 //
67 //  CLASS NAME : MediaCD
68 //
69 ///////////////////////////////////////////////////////////////////
70
71
72   ///////////////////////////////////////////////////////////////////
73   //
74   //
75   //  METHOD NAME : MediaCD::MediaCD
76   //  METHOD TYPE : Constructor
77   //
78   //  DESCRIPTION :
79   //
80   MediaCD::MediaCD( const Url &      url_r,
81                     const Pathname & attach_point_hint_r )
82     : MediaHandler( url_r, attach_point_hint_r,
83                     url_r.getPathName(), // urlpath below attachpoint
84                     false ),
85       _lastdev(-1), _lastdev_tried(-1)
86   {
87     MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")"
88         << endl;
89
90     if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
91     {
92       ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
93       ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
94     }
95
96     string devices = _url.getQueryParam("devices");
97     if (!devices.empty())
98     {
99       string::size_type pos;
100       DBG << "parse " << devices << endl;
101       while(!devices.empty())
102       {
103         pos = devices.find(',');
104         string device = devices.substr(0,pos);
105         if (!device.empty())
106         {
107           MediaSource media("cdrom", device, 0, 0);
108           _devices.push_back( media);
109           DBG << "use device (delayed verify)" << device << endl;
110         }
111         if (pos!=string::npos)
112           devices=devices.substr(pos+1);
113         else
114           devices.erase();
115       }
116     }
117     else
118     {
119       DBG << "going to use on-demand device list" << endl;
120       return;
121     }
122
123     if( _devices.empty())
124     {
125       ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
126       ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
127     }
128   }
129
130   ///////////////////////////////////////////////////////////////////
131   //
132   //
133   //  METHOD NAME : MediaCD::openTray
134   //  METHOD TYPE : bool
135   //
136   bool MediaCD::openTray( const std::string & device_r )
137   {
138     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
139     int res = -1;
140
141     if ( fd != -1)
142     {
143       res = ::ioctl( fd, CDROMEJECT );
144       ::close( fd );
145     }
146
147     if ( res )
148     {
149       if( fd == -1)
150       {
151         WAR << "Unable to open '" << device_r
152             << "' (" << ::strerror( errno ) << ")" << endl;
153       }
154       else
155       {
156         WAR << "Eject " << device_r
157             << " failed (" << ::strerror( errno ) << ")" << endl;
158       }
159
160 #if defined(EJECT_TOOL_PATH)
161       DBG << "Try to eject " << device_r << " using "
162         << EJECT_TOOL_PATH << " utility" << std::endl;
163
164       const char *cmd[3];
165       cmd[0] = EJECT_TOOL_PATH;
166       cmd[1] = device_r.c_str();
167       cmd[2] = NULL;
168       ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
169       
170       for(std::string out( eject.receiveLine());
171           out.length(); out = eject.receiveLine())
172       {
173         DBG << " " << out;
174       }
175       
176       if(eject.close() != 0)
177       {
178         WAR << "Eject of " << device_r << " failed." << std::endl;
179         return false;
180       }
181 #else
182       return false;
183 #endif
184     }
185     MIL << "Eject of " << device_r << " successful." << endl;
186     return true;
187   }
188
189   ///////////////////////////////////////////////////////////////////
190   //
191   //
192   //    METHOD NAME : MediaCD::closeTray
193   //    METHOD TYPE : bool
194   //
195   bool MediaCD::closeTray( const std::string & device_r )
196   {
197     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
198     if ( fd == -1 ) {
199       WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
200       return false;
201     }
202     int res = ::ioctl( fd, CDROMCLOSETRAY );
203     ::close( fd );
204     if ( res ) {
205       WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
206       return false;
207     }
208     DBG << "Close tray " << device_r << endl;
209     return true;
210   }
211
212   ///////////////////////////////////////////////////////////////////
213   //
214   //
215   //  METHOD NAME : MediaCD::detectDevices
216   //  METHOD TYPE : MediaCD::DeviceList
217   //
218   MediaCD::DeviceList
219   MediaCD::detectDevices(bool supportingDVD) const
220   {
221     using namespace zypp::target::hal;
222
223     DeviceList detected;
224     try
225     {
226       HalContext hal(true);
227
228       std::vector<std::string> drv_udis;
229       drv_udis = hal.findDevicesByCapability("storage.cdrom");
230       
231       DBG << "Found " << drv_udis.size() << " cdrom drive udis" << std::endl;
232       for(size_t d = 0; d < drv_udis.size(); d++)
233       {
234         HalDrive drv( hal.getDriveFromUDI( drv_udis[d]));
235       
236         if( drv)
237         {
238           bool supportsDVD=false;
239           if( supportingDVD)
240           {
241             std::vector<std::string> caps;
242             try {
243               caps = drv.getCdromCapabilityNames();
244             }
245             catch(const HalException &e)
246             {
247               ZYPP_CAUGHT(e);
248             }
249
250             std::vector<std::string>::const_iterator ci;
251             for( ci=caps.begin(); ci != caps.end(); ++ci)
252             {
253               if( *ci == "dvd")
254                 supportsDVD = true;
255             }
256           }
257
258           MediaSource media("cdrom", drv.getDeviceFile(),
259                             drv.getDeviceMajor(),
260                             drv.getDeviceMinor());
261           DBG << "Found " << drv_udis[d] << ": "
262               << media.asString() << std::endl;
263           if( supportingDVD && supportsDVD)
264           {
265             detected.push_front(media);
266           }
267           else
268           {
269             detected.push_back(media);
270           }
271         }
272       }
273     }
274     catch(const zypp::target::hal::HalException &e)
275     {
276       ZYPP_CAUGHT(e);
277     }
278
279     return detected;
280   }
281
282
283   ///////////////////////////////////////////////////////////////////
284   //
285   //
286   //  METHOD NAME : MediaCD::attachTo
287   //  METHOD TYPE : PMError
288   //
289   //  DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
290   //
291   void MediaCD::attachTo(bool next)
292   {
293     DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
294     if (next && _lastdev == -1)
295       ZYPP_THROW(MediaNotSupportedException(url()));
296
297     DeviceList detected(
298       detectDevices(_url.getScheme() == "dvd" ? true : false));
299
300     if(_devices.empty())
301     {
302       DBG << "creating on-demand device list" << endl;
303       //default is /dev/cdrom; for dvd: /dev/dvd if it exists
304       string device( "/dev/cdrom" );
305       if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
306       {
307         device = "/dev/dvd";
308       }
309
310       PathInfo dinfo(device);
311       if( dinfo.isBlk())
312       {
313         MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
314
315         DeviceList::const_iterator d( detected.begin());
316         for( ; d != detected.end(); ++d)
317         {
318           // /dev/cdrom or /dev/dvd to the front
319           if( media.equals( *d))
320             _devices.push_front( *d);
321           else
322             _devices.push_back( *d);
323         }
324       }
325       else
326       {
327         // no /dev/cdrom or /dev/dvd link
328         _devices = detected;
329       }
330     }
331
332     Mount mount;
333     string mountpoint = attachPoint().asString();
334     bool mountsucceeded = false;
335     int count = 0;
336     MediaMountException merr;
337
338     string options = _url.getQueryParam("mountoptions");
339     if (options.empty())
340     {
341       options="ro";
342     }
343
344     //TODO: make configurable
345     list<string> filesystems;
346
347     // if DVD, try UDF filesystem before iso9660
348     if ( _url.getScheme() == "dvd" )
349       filesystems.push_back("udf");
350
351     filesystems.push_back("iso9660");
352
353     // try all devices in sequence
354     for (DeviceList::iterator it = _devices.begin()
355     ; !mountsucceeded && it != _devices.end()
356     ; ++it, count++ )
357     {
358       DBG << "count " << count << endl;
359       if (next && count <=_lastdev_tried )
360       {
361         DBG << "skipping device " << it->name << endl;
362         continue;
363       }
364
365       _lastdev_tried = count;
366
367       MediaSource temp( *it);
368       bool        valid=false;
369       PathInfo    dinfo(temp.name);
370       if( dinfo.isBlk())
371       {
372         temp.maj_nr = dinfo.major();
373         temp.min_nr = dinfo.minor();
374
375         DeviceList::const_iterator d( detected.begin());
376         for( ; d != detected.end(); ++d)
377         {
378           if( temp.equals( *d))
379           {
380             valid = true;
381             break;
382           }
383         }
384       }
385       if( !valid)
386       {
387         DBG << "skipping invalid device: " << it->name << endl;
388         continue;
389       }
390
391       MediaSourceRef media( new MediaSource(temp));
392       AttachedMedia ret( findAttachedMedia( media));
393
394       if( ret.mediaSource && ret.attachPoint &&
395          !ret.attachPoint->empty())
396       {
397         DBG << "Using a shared media "
398             << ret.mediaSource->name
399             << " attached on "
400             << ret.attachPoint->path
401             << endl;
402         removeAttachPoint();
403         setAttachPoint(ret.attachPoint);
404         setMediaSource(ret.mediaSource);
405         _lastdev = count;
406         mountsucceeded = true;
407         break;
408       }
409
410 #if REUSE_FOREIGN_MOUNTS > 0
411       {
412         MediaManager  manager;
413         MountEntries  entries( manager.getMountEntries());
414         MountEntries::const_iterator e;
415         for( e = entries.begin(); e != entries.end(); ++e)
416         {
417           bool        is_device = false;
418           std::string dev_path(Pathname(e->src).asString());
419           PathInfo    dev_info;
420
421           if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
422               dev_info(e->src) && dev_info.isBlk())
423           {
424             is_device = true;
425           }
426
427           if( is_device && media->maj_nr == dev_info.major() &&
428                            media->min_nr == dev_info.minor())
429           {
430             AttachPointRef ap( new AttachPoint(e->dir, false));
431             AttachedMedia  am( media, ap);
432             //
433             // 1 = automounted only, 2 == all
434             //
435 #if REUSE_FOREIGN_MOUNTS == 1
436             if( isAutoMountedMedia(am))
437 #endif
438             {
439               DBG << "Using a system mounted media "
440                   << media->name
441                   << " attached on "
442                   << ap->path
443                   << endl;
444
445               media->iown = false; // mark attachment as foreign
446
447               setMediaSource(media);
448               setAttachPoint(ap);
449               _lastdev = count;
450               mountsucceeded = true;
451               break;
452             }
453           }
454         }
455         if( mountsucceeded)
456           break;
457       }
458 #endif  // REUSE_FOREIGN_MOUNTS
459
460       // close tray
461       closeTray( it->name );
462
463       // try all filesystems in sequence
464       for(list<string>::iterator fsit = filesystems.begin()
465           ; !mountsucceeded && fsit != filesystems.end()
466           ; ++fsit)
467       {
468         try
469         {
470           if( !isUseableAttachPoint(Pathname(mountpoint)))
471           {
472             mountpoint = createAttachPoint().asString();
473             setAttachPoint( mountpoint, true);
474             if( mountpoint.empty())
475             {
476               ZYPP_THROW( MediaBadAttachPointException(url()));
477             }
478           }
479
480           mount.mount(it->name, mountpoint, *fsit, options);
481
482           setMediaSource(media);
483
484           // wait for /etc/mtab update ...
485           // (shouldn't be needed)
486           int limit = 5;
487           while( !(mountsucceeded=isAttached()) && --limit)
488           {
489             sleep(1);
490           }
491
492           if( mountsucceeded)
493           {
494             _lastdev = count;
495           }
496           else
497           {
498             setMediaSource(MediaSourceRef());
499             try
500             {
501               mount.umount(attachPoint().asString());
502             }
503             catch (const MediaException & excpt_r)
504             {
505               ZYPP_CAUGHT(excpt_r);
506             }
507             ZYPP_THROW(MediaMountException(
508               "Unable to verify that the media was mounted",
509               it->name, mountpoint
510             ));
511           }
512         }
513         catch (const MediaMountException &e)
514         {
515           merr = e;
516           removeAttachPoint();
517           ZYPP_CAUGHT(e);
518         }
519         catch (const MediaException & excpt_r)
520         {
521           removeAttachPoint();
522           ZYPP_CAUGHT(excpt_r);
523         }
524       } // for filesystems
525     } // for _devices
526
527     if (!mountsucceeded)
528     {
529       _lastdev = -1;
530
531       if( !merr.mountOutput().empty())
532       {
533         ZYPP_THROW(MediaMountException(merr.mountError(),
534                                        _url.asString(),
535                                        mountpoint,
536                                        merr.mountOutput()));
537       }
538       else
539       {
540         ZYPP_THROW(MediaMountException("Mounting media failed",
541                                        _url.asString(), mountpoint));
542       }
543     }
544     DBG << _lastdev << " " << count << endl;
545   }
546
547
548   ///////////////////////////////////////////////////////////////////
549   //
550   //
551   //  METHOD NAME : MediaCD::releaseFrom
552   //  METHOD TYPE : PMError
553   //
554   //  DESCRIPTION : Asserted that media is attached.
555   //
556   void MediaCD::releaseFrom( const std::string & ejectDev )
557   {
558     Mount mount;
559     try
560     {
561       AttachedMedia am( attachedMedia());
562       if(am.mediaSource && am.mediaSource->iown)
563         mount.umount(am.attachPoint->path.asString());
564     }
565     catch (const Exception & excpt_r)
566     {
567       ZYPP_CAUGHT(excpt_r);
568       if (!ejectDev.empty())
569       {
570 #if FORCE_RELEASE_FOREIGN > 0
571         /* 1 = automounted only, 2 = all */
572         forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
573 #endif
574         if(openTray( ejectDev ))
575           return;
576       }
577       ZYPP_RETHROW(excpt_r);
578     }
579
580     // eject device
581     if (!ejectDev.empty())
582     {
583 #if FORCE_RELEASE_FOREIGN > 0
584       /* 1 = automounted only, 2 = all */
585       forceRelaseAllMedia(false, FORCE_RELEASE_FOREIGN == 1);
586 #endif
587       if( !openTray( ejectDev ))
588       {
589 #if REPORT_EJECT_ERRORS
590         ZYPP_THROW(MediaNotEjectedException(ejectDev));
591 #endif
592       }
593     }
594   }
595
596   ///////////////////////////////////////////////////////////////////
597   //
598   //
599   //  METHOD NAME : MediaCD::forceEject
600   //  METHOD TYPE : void
601   //
602   // Asserted that media is not attached.
603   //
604   void MediaCD::forceEject(const std::string & ejectDev)
605   {
606     bool ejected=false;
607     if ( !isAttached()) {       // no device mounted in this instance
608
609       DeviceList detected(
610           detectDevices(_url.getScheme() == "dvd" ? true : false));
611
612       if(_devices.empty())
613       {
614         DBG << "creating on-demand device list" << endl;
615         //default is /dev/cdrom; for dvd: /dev/dvd if it exists
616         string device( "/dev/cdrom" );
617         if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
618           device = "/dev/dvd";
619         }
620
621         PathInfo dinfo(device);
622         if( dinfo.isBlk())
623         {
624           MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
625
626           DeviceList::const_iterator d( detected.begin());
627           for( ; d != detected.end(); ++d)
628           {
629             // /dev/cdrom or /dev/dvd to the front
630             if( media.equals( *d))
631               _devices.push_front( *d);
632             else
633               _devices.push_back( *d);
634           }
635         }
636         else
637         {
638           // no /dev/cdrom or /dev/dvd link
639           _devices = detected;
640         }
641       }
642
643       DeviceList::iterator it;
644       for( it = _devices.begin(); it != _devices.end(); ++it ) {
645         MediaSourceRef media( new MediaSource( *it));
646         if (media->name != ejectDev)
647           continue;
648
649         bool        valid=false;
650         PathInfo    dinfo(media->name);
651         if( dinfo.isBlk())
652         {
653           media->maj_nr = dinfo.major();
654           media->min_nr = dinfo.minor();
655
656           DeviceList::const_iterator d( detected.begin());
657           for( ; d != detected.end(); ++d)
658           {
659             if( media->equals( *d))
660             {
661               valid = true;
662               break;
663             }
664           }
665         }
666         if( !valid)
667         {
668           DBG << "skipping invalid device: " << it->name << endl;
669           continue;
670         }
671
672         // FIXME: we have also to check if it is mounted in the system
673         AttachedMedia ret( findAttachedMedia( media));
674         if( !ret.mediaSource)
675         {
676 #if FORCE_RELEASE_FOREIGN > 0
677           /* 1 = automounted only, 2 = all */
678           forceRelaseAllMedia(media, false, FORCE_RELEASE_FOREIGN == 1);
679 #endif
680           if ( openTray( it->name ) )
681           {
682             ejected = true;
683             break; // on 1st success
684           }
685         }
686       }
687     }
688     if( !ejected)
689     {
690 #if REPORT_EJECT_ERRORS
691       ZYPP_THROW(MediaNotEjectedException());
692 #endif
693     }
694   }
695
696   bool MediaCD::isAutoMountedMedia(const AttachedMedia &media)
697   {
698     bool is_automounted = false;
699     if( media.mediaSource && !media.mediaSource->name.empty())
700     {
701       using namespace zypp::target::hal;
702
703       try
704       {
705         HalContext hal(true);
706
707         HalVolume vol = hal.getVolumeFromDeviceFile(media.mediaSource->name);
708         if( vol)
709         {
710           std::string udi = vol.getUDI();
711           std::string key;
712           std::string mnt;
713
714           try
715           {
716             key = "info.hal_mount.created_mount_point";
717             mnt = hal.getDevicePropertyString(udi, key);
718
719             if(media.attachPoint->path == mnt)
720               is_automounted = true;
721           }
722           catch(const HalException &e1)
723           {
724             ZYPP_CAUGHT(e1);
725
726             try
727             {
728               key = "volume.mount_point";
729               mnt = hal.getDevicePropertyString(udi, key);
730
731               if(media.attachPoint->path == mnt)
732                 is_automounted = true;
733             }
734             catch(const HalException &e2)
735             {
736               ZYPP_CAUGHT(e2);
737             }
738           }
739         }
740       }
741       catch(const HalException &e)
742       {
743         ZYPP_CAUGHT(e);
744       }
745     }
746     DBG << "Media "        << media.mediaSource->asString()
747         << " attached on " << media.attachPoint->path
748         << " is"           << (is_automounted ? "" : " not")
749         << " automounted"  << std::endl;
750     return is_automounted;
751   }
752
753   ///////////////////////////////////////////////////////////////////
754   //
755   //  METHOD NAME : MediaCD::isAttached
756   //  METHOD TYPE : bool
757   //
758   //  DESCRIPTION : Override check if media is attached.
759   //
760   bool
761   MediaCD::isAttached() const
762   {
763     return checkAttached(false);
764   }
765
766   ///////////////////////////////////////////////////////////////////
767   //
768   //  METHOD NAME : MediaCD::getFile
769   //  METHOD TYPE : PMError
770   //
771   //  DESCRIPTION : Asserted that media is attached.
772   //
773   void MediaCD::getFile( const Pathname & filename ) const
774   {
775     MediaHandler::getFile( filename );
776   }
777
778   ///////////////////////////////////////////////////////////////////
779   //
780   //  METHOD NAME : MediaCD::getDir
781   //  METHOD TYPE : PMError
782   //
783   //  DESCRIPTION : Asserted that media is attached.
784   //
785   void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
786   {
787     MediaHandler::getDir( dirname, recurse_r );
788   }
789
790   ///////////////////////////////////////////////////////////////////
791   //
792   //
793   //  METHOD NAME : MediaCD::getDirInfo
794   //  METHOD TYPE : PMError
795   //
796   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
797   //
798   void MediaCD::getDirInfo( std::list<std::string> & retlist,
799                             const Pathname & dirname, bool dots ) const
800   {
801     MediaHandler::getDirInfo( retlist, dirname, dots );
802   }
803
804   ///////////////////////////////////////////////////////////////////
805   //
806   //
807   //  METHOD NAME : MediaCD::getDirInfo
808   //  METHOD TYPE : PMError
809   //
810   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
811   //
812   void MediaCD::getDirInfo( filesystem::DirContent & retlist,
813                             const Pathname & dirname, bool dots ) const
814   {
815     MediaHandler::getDirInfo( retlist, dirname, dots );
816   }
817
818   bool MediaCD::getDoesFileExist( const Pathname & filename ) const
819   {
820     return MediaHandler::getDoesFileExist( filename );
821   }
822
823   bool MediaCD::hasMoreDevices()
824   {
825     if (_devices.size() == 0)
826       return false;
827     else if (_lastdev_tried < 0)
828       return true;
829
830     return (unsigned) _lastdev_tried < _devices.size() - 1;
831   }
832
833   void
834   MediaCD::getDetectedDevices(std::vector<std::string> & devices,
835                               unsigned int & index) const
836   {
837     index = 0;
838     if (!devices.empty())
839       devices.clear();
840
841     for (DeviceList::const_iterator it = _devices.begin();
842          it != _devices.end(); ++it)
843       devices.push_back(it->name);
844
845     if (_lastdev >= 0)
846       index = _lastdev;
847
848     // try to detect again if _devices are empty (maybe this method will be
849     // called before _devices get actually filled)
850     if (devices.empty())
851     {
852       DBG << "no device list so far, trying to detect" << endl;
853
854       DeviceList detected(
855         detectDevices(_url.getScheme() == "dvd" ? true : false));
856
857       for (DeviceList::const_iterator it = detected.begin();
858            it != detected.end(); ++it)
859         devices.push_back(it->name);
860
861       // don't know which one is in use in this case
862       index = 0;
863     }
864
865     MIL << "got " << devices.size() << " detected devices, current: "
866         << (index < devices.size() ? devices[index] : "<none>")
867         << "(" << index << ")" << endl;
868   }
869
870
871   } // namespace media
872 } // namespace zypp
873 // vim: set ts=8 sts=2 sw=2 ai noet: