libzypp  11.13.5
MediaCD.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
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
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"
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 1
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 
56 namespace zypp
57 {
59  namespace media
60  {
61 
63  namespace
64  {
65  typedef std::list<MediaSource> DeviceList;
66 
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.major(), devnode.minor() );
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
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;
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;
241  }
242  }
243 
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;
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 
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.major(), dvdinfo.minor() );
339  DBG << "Found (GUESS): " << media << std::endl;
340  detected.push_back( media );
341  }
342  if ( cdrinfo.isBlk()
343  && ! ( cdrinfo.major() == dvdinfo.major() && cdrinfo.minor() == dvdinfo.minor() ) )
344  {
345  MediaSource media( "cdrom", cdrinfo.path().asString(), cdrinfo.major(), cdrinfo.minor() );
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.major(), dinfo.minor() );
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 
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 )
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.major();
457  temp.min_nr = dinfo.minor();
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;
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.major() &&
494  media->min_nr == dev_info.minor())
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  {
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  {
559  try
560  {
561  mount.umount(attachPoint().asString());
562  }
563  catch (const MediaException & excpt_r)
564  {
565  ZYPP_CAUGHT(excpt_r);
566  }
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;
577  ZYPP_CAUGHT(e);
578  }
579  catch (const MediaException & excpt_r)
580  {
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  {
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 
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  {
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
645 #endif
646  }
647  }
648  }
649 
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  bool ejected = false;
661  if ( ! isAttached() ) // no device mounted in this instance
662  {
663  // This also fills the _devices list on demand
664  DeviceList detected( detectDevices( _url.getScheme() == "dvd" ? true : false ) );
665  for_( it, _devices.begin(), _devices.end() )
666  {
667  MediaSourceRef media( new MediaSource( *it ) );
668  if ( media->name != ejectDev_r )
669  continue;
670 
671  // bnc#755815: _devices contains either devices passed as url option
672  // or autodetected ones. Accept both as long as they are block
673  // devices.
674  PathInfo dinfo( media->name );
675  if( ! dinfo.isBlk() )
676  {
677  WAR << "skipping non block device: " << dinfo << endl;
678  continue;
679  }
680  DBG << "trying device " << dinfo << endl;
681 
682  // FIXME: we have also to check if it is mounted in the system
683  AttachedMedia ret( findAttachedMedia( media));
684  if( !ret.mediaSource )
685  {
686  forceRelaseAllMedia( media, false );
687  if ( openTray( it->name ) )
688  {
689  ejected = true;
690  break; // on 1st success
691  }
692  }
693  }
694  }
695 #if REPORT_EJECT_ERRORS
696  if( !ejected)
697  {
699  }
700 #endif
701  }
702 
704  //
705  // METHOD NAME : MediaCD::isAttached
706  // METHOD TYPE : bool
707  //
708  // DESCRIPTION : Override check if media is attached.
709  //
710  bool
712  {
713  return checkAttached(false);
714  }
715 
717  //
718  // METHOD NAME : MediaCD::getFile
719  // METHOD TYPE : PMError
720  //
721  // DESCRIPTION : Asserted that media is attached.
722  //
723  void MediaCD::getFile( const Pathname & filename ) const
724  {
725  MediaHandler::getFile( filename );
726  }
727 
729  //
730  // METHOD NAME : MediaCD::getDir
731  // METHOD TYPE : PMError
732  //
733  // DESCRIPTION : Asserted that media is attached.
734  //
735  void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
736  {
737  MediaHandler::getDir( dirname, recurse_r );
738  }
739 
741  //
742  //
743  // METHOD NAME : MediaCD::getDirInfo
744  // METHOD TYPE : PMError
745  //
746  // DESCRIPTION : Asserted that media is attached and retlist is empty.
747  //
748  void MediaCD::getDirInfo( std::list<std::string> & retlist,
749  const Pathname & dirname, bool dots ) const
750  {
751  MediaHandler::getDirInfo( retlist, dirname, dots );
752  }
753 
755  //
756  //
757  // METHOD NAME : MediaCD::getDirInfo
758  // METHOD TYPE : PMError
759  //
760  // DESCRIPTION : Asserted that media is attached and retlist is empty.
761  //
762  void MediaCD::getDirInfo( filesystem::DirContent & retlist, const Pathname & dirname, bool dots ) const
763  {
764  MediaHandler::getDirInfo( retlist, dirname, dots );
765  }
766 
767 
768  bool MediaCD::getDoesFileExist( const Pathname & filename ) const
769  {
770  return MediaHandler::getDoesFileExist( filename );
771  }
772 
773 
775  {
776  if (_devices.size() == 0)
777  return false;
778  else if (_lastdev_tried < 0)
779  return true;
780 
781  return (unsigned) _lastdev_tried < _devices.size() - 1;
782  }
783 
784 
785  void MediaCD::getDetectedDevices( std::vector<std::string> & devices, unsigned int & index ) const
786  {
787  if ( ! devices.empty() )
788  devices.clear();
789 
790  if ( _devices.empty() )
791  // This also fills the _devices list on demand
792  detectDevices( _url.getScheme() == "dvd" ? true : false );
793 
794  for ( const auto & it : _devices )
795  devices.push_back( it.name );
796 
797  index = ( _lastdev >= 0 ? (unsigned)_lastdev : 0 );
798 
799  MIL << "got " << devices.size() << " detected devices, current: "
800  << (index < devices.size() ? devices[index] : "<none>")
801  << "(" << index << ")" << endl;
802  }
803 
804  } // namespace media
806 } // namespace zypp