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