libzypp 8.13.6

MediaCD.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 extern "C"
00013 {
00014 #include <sys/ioctl.h>
00015 #include <linux/cdrom.h>
00016 #include <libudev.h>
00017 }
00018 
00019 #include <cstring> // strerror
00020 #include <cstdlib> // getenv
00021 #include <iostream>
00022 
00023 #include "zypp/base/Logger.h"
00024 #include "zypp/ExternalProgram.h"
00025 #include "zypp/media/Mount.h"
00026 #include "zypp/media/MediaCD.h"
00027 #include "zypp/media/MediaManager.h"
00028 #include "zypp/Url.h"
00029 #include "zypp/AutoDispose.h"
00030 
00031 
00032 
00033 /*
00034 ** if to throw exception on eject errors or ignore them
00035 */
00036 #define  REPORT_EJECT_ERRORS     1
00037 
00038 /*
00039 ** If defined to the full path of the eject utility,
00040 ** it will be used additionally to the eject-ioctl.
00041 */
00042 #define EJECT_TOOL_PATH "/bin/eject"
00043 
00044 
00045 using namespace std;
00046 
00047 namespace zypp {
00048   namespace media {
00049 
00051 //
00052 //  CLASS NAME : MediaCD
00053 //
00055 
00056 
00058   //
00059   //
00060   //  METHOD NAME : MediaCD::MediaCD
00061   //  METHOD TYPE : Constructor
00062   //
00063   //  DESCRIPTION :
00064   //
00065   MediaCD::MediaCD( const Url &      url_r,
00066                     const Pathname & attach_point_hint_r )
00067     : MediaHandler( url_r, attach_point_hint_r,
00068                     url_r.getPathName(), // urlpath below attachpoint
00069                     false ),
00070       _lastdev(-1), _lastdev_tried(-1)
00071   {
00072     MIL << "MediaCD::MediaCD(" << url_r << ", " << attach_point_hint_r << ")"
00073         << endl;
00074 
00075     if( url_r.getScheme() != "dvd" && url_r.getScheme() != "cd")
00076     {
00077       ERR << "Unsupported schema in the Url: " << url_r.asString() << endl;
00078       ZYPP_THROW(MediaUnsupportedUrlSchemeException(_url));
00079     }
00080 
00081     string devices = _url.getQueryParam("devices");
00082     if (!devices.empty())
00083     {
00084       string::size_type pos;
00085       DBG << "parse " << devices << endl;
00086       while(!devices.empty())
00087       {
00088         pos = devices.find(',');
00089         string device = devices.substr(0,pos);
00090         if (!device.empty())
00091         {
00092           MediaSource media("cdrom", device, 0, 0);
00093           _devices.push_back( media);
00094           DBG << "use device (delayed verify)" << device << endl;
00095         }
00096         if (pos!=string::npos)
00097           devices=devices.substr(pos+1);
00098         else
00099           devices.erase();
00100       }
00101     }
00102     else
00103     {
00104       DBG << "going to use on-demand device list" << endl;
00105       return;
00106     }
00107 
00108     if( _devices.empty())
00109     {
00110       ERR << "Unable to find any cdrom drive for " << _url.asString() << endl;
00111       ZYPP_THROW(MediaBadUrlEmptyDestinationException(_url));
00112     }
00113   }
00114 
00116   //
00117   //
00118   //  METHOD NAME : MediaCD::openTray
00119   //  METHOD TYPE : bool
00120   //
00121   bool MediaCD::openTray( const std::string & device_r )
00122   {
00123     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
00124     int res = -1;
00125 
00126     if ( fd != -1)
00127     {
00128       res = ::ioctl( fd, CDROMEJECT );
00129       ::close( fd );
00130     }
00131 
00132     if ( res )
00133     {
00134       if( fd == -1)
00135       {
00136         WAR << "Unable to open '" << device_r
00137             << "' (" << ::strerror( errno ) << ")" << endl;
00138       }
00139       else
00140       {
00141         WAR << "Eject " << device_r
00142             << " failed (" << ::strerror( errno ) << ")" << endl;
00143       }
00144 
00145 #if defined(EJECT_TOOL_PATH)
00146       DBG << "Try to eject " << device_r << " using "
00147         << EJECT_TOOL_PATH << " utility" << std::endl;
00148 
00149       const char *cmd[3];
00150       cmd[0] = EJECT_TOOL_PATH;
00151       cmd[1] = device_r.c_str();
00152       cmd[2] = NULL;
00153       ExternalProgram eject(cmd, ExternalProgram::Stderr_To_Stdout);
00154 
00155       for(std::string out( eject.receiveLine());
00156           out.length(); out = eject.receiveLine())
00157       {
00158         DBG << " " << out;
00159       }
00160 
00161       if(eject.close() != 0)
00162       {
00163         WAR << "Eject of " << device_r << " failed." << std::endl;
00164         return false;
00165       }
00166 #else
00167       return false;
00168 #endif
00169     }
00170     MIL << "Eject of " << device_r << " successful." << endl;
00171     return true;
00172   }
00173 
00175   //
00176   //
00177   //    METHOD NAME : MediaCD::closeTray
00178   //    METHOD TYPE : bool
00179   //
00180   bool MediaCD::closeTray( const std::string & device_r )
00181   {
00182     int fd = ::open( device_r.c_str(), O_RDONLY|O_NONBLOCK );
00183     if ( fd == -1 ) {
00184       WAR << "Unable to open '" << device_r << "' (" << ::strerror( errno ) << ")" << endl;
00185       return false;
00186     }
00187     int res = ::ioctl( fd, CDROMCLOSETRAY );
00188     ::close( fd );
00189     if ( res ) {
00190       WAR << "Close tray " << device_r << " failed (" << ::strerror( errno ) << ")" << endl;
00191       return false;
00192     }
00193     DBG << "Close tray " << device_r << endl;
00194     return true;
00195   }
00196 
00198   //
00199   //
00200   //  METHOD NAME : MediaCD::detectDevices
00201   //  METHOD TYPE : MediaCD::DeviceList
00202   //
00203   MediaCD::DeviceList MediaCD::detectDevices( bool supportingDVD ) const
00204   {
00205     // http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/index.html
00206     zypp::AutoDispose<struct udev *> udev( ::udev_new(), ::udev_unref );
00207     if ( ! udev )
00208     {
00209       ERR << "Can't create udev context." << endl;
00210       return DeviceList();
00211     }
00212 
00213     zypp::AutoDispose<struct udev_enumerate *> enumerate( ::udev_enumerate_new(udev), ::udev_enumerate_unref );
00214     if ( ! enumerate )
00215     {
00216       ERR << "Can't create udev list entry." << endl;
00217       return DeviceList();
00218     }
00219 
00220     ::udev_enumerate_add_match_subsystem( enumerate, "block" );
00221     ::udev_enumerate_add_match_property( enumerate, "ID_CDROM", "1" );
00222     ::udev_enumerate_scan_devices( enumerate );
00223 
00224     DeviceList detected;
00225     struct udev_list_entry * entry = 0;
00226     udev_list_entry_foreach( entry, ::udev_enumerate_get_list_entry( enumerate ) )
00227     {
00228       zypp::AutoDispose<struct udev_device *> device( ::udev_device_new_from_syspath( ::udev_enumerate_get_udev( enumerate ),
00229                                                                                       ::udev_list_entry_get_name( entry ) ),
00230                                                       ::udev_device_unref );
00231       if ( ! device )
00232       {
00233         ERR << "Can't create udev device." << endl;
00234         continue;
00235       }
00236 
00237       if ( supportingDVD && ! ::udev_device_get_property_value( device, "ID_CDROM_DVD" ) )
00238       {
00239         continue;       // looking for dvd only
00240       }
00241 
00242       const char * devnodePtr( ::udev_device_get_devnode( device ) );
00243       if ( ! devnodePtr )
00244       {
00245         ERR << "Got NULL devicenode." << endl;
00246         continue;
00247       }
00248 
00249       // In case we need it someday:
00250       //const char * mountpath = ::udev_device_get_property_value( device, "FSTAB_DIR" );
00251 
00252       PathInfo devnode( devnodePtr );
00253       if ( devnode.isBlk() )
00254       {
00255         MediaSource media( "cdrom", devnode.path().asString(), devnode.major(), devnode.minor() );
00256         DBG << "Found (udev): " << media << std::endl;
00257         detected.push_back( media );
00258       }
00259     }
00260     if ( detected.empty() )
00261       WAR << "Did not find any CD/DVD device." << endl;
00262     return detected;
00263   }
00264 
00265 
00267   //
00268   //
00269   //  METHOD NAME : MediaCD::attachTo
00270   //  METHOD TYPE : PMError
00271   //
00272   //  DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
00273   //
00274   void MediaCD::attachTo(bool next)
00275   {
00276     DBG << "next " << next << " last " << _lastdev << " last tried " << _lastdev_tried << endl;
00277     if (next && _lastdev == -1)
00278       ZYPP_THROW(MediaNotSupportedException(url()));
00279 
00280     DeviceList detected(
00281       detectDevices(_url.getScheme() == "dvd" ? true : false));
00282 
00283     if(_devices.empty())
00284     {
00285       DBG << "creating on-demand device list" << endl;
00286       //default is /dev/cdrom; for dvd: /dev/dvd if it exists
00287       string device( "/dev/cdrom" );
00288       if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() )
00289       {
00290         device = "/dev/dvd";
00291       }
00292 
00293       PathInfo dinfo(device);
00294       if( dinfo.isBlk())
00295       {
00296         MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
00297 
00298         DeviceList::const_iterator d( detected.begin());
00299         for( ; d != detected.end(); ++d)
00300         {
00301           // /dev/cdrom or /dev/dvd to the front
00302           if( media.equals( *d))
00303             _devices.push_front( *d);
00304           else
00305             _devices.push_back( *d);
00306         }
00307       }
00308       else
00309       {
00310         // no /dev/cdrom or /dev/dvd link
00311         _devices = detected;
00312       }
00313     }
00314 
00315     Mount mount;
00316     string mountpoint = attachPoint().asString();
00317     bool mountsucceeded = false;
00318     int count = 0;
00319     MediaMountException merr;
00320 
00321     string options = _url.getQueryParam("mountoptions");
00322     if (options.empty())
00323     {
00324       options="ro";
00325     }
00326 
00327     //TODO: make configurable
00328     list<string> filesystems;
00329 
00330     // if DVD, try UDF filesystem before iso9660
00331     if ( _url.getScheme() == "dvd" )
00332       filesystems.push_back("udf");
00333 
00334     filesystems.push_back("iso9660");
00335 
00336     // try all devices in sequence
00337     for (DeviceList::iterator it = _devices.begin()
00338     ; !mountsucceeded && it != _devices.end()
00339     ; ++it, count++ )
00340     {
00341       DBG << "count " << count << endl;
00342       if (next && count <=_lastdev_tried )
00343       {
00344         DBG << "skipping device " << it->name << endl;
00345         continue;
00346       }
00347 
00348       _lastdev_tried = count;
00349 
00350       MediaSource temp( *it);
00351       bool        valid=false;
00352       PathInfo    dinfo(temp.name);
00353       if( dinfo.isBlk())
00354       {
00355         temp.maj_nr = dinfo.major();
00356         temp.min_nr = dinfo.minor();
00357 
00358         DeviceList::const_iterator d( detected.begin());
00359         for( ; d != detected.end(); ++d)
00360         {
00361           if( temp.equals( *d))
00362           {
00363             valid = true;
00364             break;
00365           }
00366         }
00367       }
00368       if( !valid)
00369       {
00370         DBG << "skipping invalid device: " << it->name << endl;
00371         continue;
00372       }
00373 
00374       MediaSourceRef media( new MediaSource(temp));
00375       AttachedMedia ret( findAttachedMedia( media));
00376 
00377       if( ret.mediaSource && ret.attachPoint &&
00378          !ret.attachPoint->empty())
00379       {
00380         DBG << "Using a shared media "
00381             << ret.mediaSource->name
00382             << " attached on "
00383             << ret.attachPoint->path
00384             << endl;
00385         removeAttachPoint();
00386         setAttachPoint(ret.attachPoint);
00387         setMediaSource(ret.mediaSource);
00388         _lastdev = count;
00389         mountsucceeded = true;
00390         break;
00391       }
00392 
00393       {
00394         MediaManager  manager;
00395         MountEntries  entries( manager.getMountEntries());
00396         MountEntries::const_iterator e;
00397         for( e = entries.begin(); e != entries.end(); ++e)
00398         {
00399           bool        is_device = false;
00400           std::string dev_path(Pathname(e->src).asString());
00401           PathInfo    dev_info;
00402 
00403           if( dev_path.compare(0, sizeof("/dev/")-1, "/dev/") == 0 &&
00404               dev_info(e->src) && dev_info.isBlk())
00405           {
00406             is_device = true;
00407           }
00408 
00409           if( is_device && media->maj_nr == dev_info.major() &&
00410                            media->min_nr == dev_info.minor())
00411           {
00412             AttachPointRef ap( new AttachPoint(e->dir, false));
00413             AttachedMedia  am( media, ap);
00414             {
00415               DBG << "Using a system mounted media "
00416                   << media->name
00417                   << " attached on "
00418                   << ap->path
00419                   << endl;
00420 
00421               media->iown = false; // mark attachment as foreign
00422 
00423               setMediaSource(media);
00424               setAttachPoint(ap);
00425               _lastdev = count;
00426               mountsucceeded = true;
00427               break;
00428             }
00429           }
00430         }
00431         if( mountsucceeded)
00432           break;
00433       }
00434 
00435       // close tray
00436       closeTray( it->name );
00437 
00438       // try all filesystems in sequence
00439       for(list<string>::iterator fsit = filesystems.begin()
00440           ; !mountsucceeded && fsit != filesystems.end()
00441           ; ++fsit)
00442       {
00443         try
00444         {
00445           if( !isUseableAttachPoint(Pathname(mountpoint)))
00446           {
00447             mountpoint = createAttachPoint().asString();
00448             setAttachPoint( mountpoint, true);
00449             if( mountpoint.empty())
00450             {
00451               ZYPP_THROW( MediaBadAttachPointException(url()));
00452             }
00453           }
00454 
00455           mount.mount(it->name, mountpoint, *fsit, options);
00456 
00457           setMediaSource(media);
00458 
00459           // wait for /etc/mtab update ...
00460           // (shouldn't be needed)
00461           int limit = 5;
00462           while( !(mountsucceeded=isAttached()) && --limit)
00463           {
00464             sleep(1);
00465           }
00466 
00467           if( mountsucceeded)
00468           {
00469             _lastdev = count;
00470           }
00471           else
00472           {
00473             setMediaSource(MediaSourceRef());
00474             try
00475             {
00476               mount.umount(attachPoint().asString());
00477             }
00478             catch (const MediaException & excpt_r)
00479             {
00480               ZYPP_CAUGHT(excpt_r);
00481             }
00482             ZYPP_THROW(MediaMountException(
00483               "Unable to verify that the media was mounted",
00484               it->name, mountpoint
00485             ));
00486           }
00487         }
00488         catch (const MediaMountException &e)
00489         {
00490           merr = e;
00491           removeAttachPoint();
00492           ZYPP_CAUGHT(e);
00493         }
00494         catch (const MediaException & excpt_r)
00495         {
00496           removeAttachPoint();
00497           ZYPP_CAUGHT(excpt_r);
00498         }
00499       } // for filesystems
00500     } // for _devices
00501 
00502     if (!mountsucceeded)
00503     {
00504       _lastdev = -1;
00505 
00506       if( !merr.mountOutput().empty())
00507       {
00508         ZYPP_THROW(MediaMountException(merr.mountError(),
00509                                        _url.asString(),
00510                                        mountpoint,
00511                                        merr.mountOutput()));
00512       }
00513       else
00514       {
00515         ZYPP_THROW(MediaMountException("Mounting media failed",
00516                                        _url.asString(), mountpoint));
00517       }
00518     }
00519     DBG << _lastdev << " " << count << endl;
00520   }
00521 
00522 
00524   //
00525   //
00526   //  METHOD NAME : MediaCD::releaseFrom
00527   //  METHOD TYPE : PMError
00528   //
00529   //  DESCRIPTION : Asserted that media is attached.
00530   //
00531   void MediaCD::releaseFrom( const std::string & ejectDev )
00532   {
00533     Mount mount;
00534     try
00535     {
00536       AttachedMedia am( attachedMedia());
00537       if(am.mediaSource && am.mediaSource->iown)
00538         mount.umount(am.attachPoint->path.asString());
00539     }
00540     catch (const Exception & excpt_r)
00541     {
00542       ZYPP_CAUGHT(excpt_r);
00543       if (!ejectDev.empty())
00544       {
00545         forceRelaseAllMedia(false);
00546         if(openTray( ejectDev ))
00547           return;
00548       }
00549       ZYPP_RETHROW(excpt_r);
00550     }
00551 
00552     // eject device
00553     if (!ejectDev.empty())
00554     {
00555       forceRelaseAllMedia(false);
00556       if( !openTray( ejectDev ))
00557       {
00558 #if REPORT_EJECT_ERRORS
00559         ZYPP_THROW(MediaNotEjectedException(ejectDev));
00560 #endif
00561       }
00562     }
00563   }
00564 
00566   //
00567   //
00568   //  METHOD NAME : MediaCD::forceEject
00569   //  METHOD TYPE : void
00570   //
00571   // Asserted that media is not attached.
00572   //
00573   void MediaCD::forceEject(const std::string & ejectDev)
00574   {
00575     bool ejected=false;
00576     if ( !isAttached()) {       // no device mounted in this instance
00577 
00578       DeviceList detected(
00579           detectDevices(_url.getScheme() == "dvd" ? true : false));
00580 
00581       if(_devices.empty())
00582       {
00583         DBG << "creating on-demand device list" << endl;
00584         //default is /dev/cdrom; for dvd: /dev/dvd if it exists
00585         string device( "/dev/cdrom" );
00586         if ( _url.getScheme() == "dvd" && PathInfo( "/dev/dvd" ).isBlk() ) {
00587           device = "/dev/dvd";
00588         }
00589 
00590         PathInfo dinfo(device);
00591         if( dinfo.isBlk())
00592         {
00593           MediaSource media("cdrom", device, dinfo.major(), dinfo.minor());
00594 
00595           DeviceList::const_iterator d( detected.begin());
00596           for( ; d != detected.end(); ++d)
00597           {
00598             // /dev/cdrom or /dev/dvd to the front
00599             if( media.equals( *d))
00600               _devices.push_front( *d);
00601             else
00602               _devices.push_back( *d);
00603           }
00604         }
00605         else
00606         {
00607           // no /dev/cdrom or /dev/dvd link
00608           _devices = detected;
00609         }
00610       }
00611 
00612       DeviceList::iterator it;
00613       for( it = _devices.begin(); it != _devices.end(); ++it ) {
00614         MediaSourceRef media( new MediaSource( *it));
00615         if (media->name != ejectDev)
00616           continue;
00617 
00618         bool        valid=false;
00619         PathInfo    dinfo(media->name);
00620         if( dinfo.isBlk())
00621         {
00622           media->maj_nr = dinfo.major();
00623           media->min_nr = dinfo.minor();
00624 
00625           DeviceList::const_iterator d( detected.begin());
00626           for( ; d != detected.end(); ++d)
00627           {
00628             if( media->equals( *d))
00629             {
00630               valid = true;
00631               break;
00632             }
00633           }
00634         }
00635         if( !valid)
00636         {
00637           DBG << "skipping invalid device: " << it->name << endl;
00638           continue;
00639         }
00640 
00641         // FIXME: we have also to check if it is mounted in the system
00642         AttachedMedia ret( findAttachedMedia( media));
00643         if( !ret.mediaSource)
00644         {
00645           forceRelaseAllMedia(media, false);
00646           if ( openTray( it->name ) )
00647           {
00648             ejected = true;
00649             break; // on 1st success
00650           }
00651         }
00652       }
00653     }
00654     if( !ejected)
00655     {
00656 #if REPORT_EJECT_ERRORS
00657       ZYPP_THROW(MediaNotEjectedException());
00658 #endif
00659     }
00660   }
00661 
00663   //
00664   //  METHOD NAME : MediaCD::isAttached
00665   //  METHOD TYPE : bool
00666   //
00667   //  DESCRIPTION : Override check if media is attached.
00668   //
00669   bool
00670   MediaCD::isAttached() const
00671   {
00672     return checkAttached(false);
00673   }
00674 
00676   //
00677   //  METHOD NAME : MediaCD::getFile
00678   //  METHOD TYPE : PMError
00679   //
00680   //  DESCRIPTION : Asserted that media is attached.
00681   //
00682   void MediaCD::getFile( const Pathname & filename ) const
00683   {
00684     MediaHandler::getFile( filename );
00685   }
00686 
00688   //
00689   //  METHOD NAME : MediaCD::getDir
00690   //  METHOD TYPE : PMError
00691   //
00692   //  DESCRIPTION : Asserted that media is attached.
00693   //
00694   void MediaCD::getDir( const Pathname & dirname, bool recurse_r ) const
00695   {
00696     MediaHandler::getDir( dirname, recurse_r );
00697   }
00698 
00700   //
00701   //
00702   //  METHOD NAME : MediaCD::getDirInfo
00703   //  METHOD TYPE : PMError
00704   //
00705   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
00706   //
00707   void MediaCD::getDirInfo( std::list<std::string> & retlist,
00708                             const Pathname & dirname, bool dots ) const
00709   {
00710     MediaHandler::getDirInfo( retlist, dirname, dots );
00711   }
00712 
00714   //
00715   //
00716   //  METHOD NAME : MediaCD::getDirInfo
00717   //  METHOD TYPE : PMError
00718   //
00719   //  DESCRIPTION : Asserted that media is attached and retlist is empty.
00720   //
00721   void MediaCD::getDirInfo( filesystem::DirContent & retlist,
00722                             const Pathname & dirname, bool dots ) const
00723   {
00724     MediaHandler::getDirInfo( retlist, dirname, dots );
00725   }
00726 
00727   bool MediaCD::getDoesFileExist( const Pathname & filename ) const
00728   {
00729     return MediaHandler::getDoesFileExist( filename );
00730   }
00731 
00732   bool MediaCD::hasMoreDevices()
00733   {
00734     if (_devices.size() == 0)
00735       return false;
00736     else if (_lastdev_tried < 0)
00737       return true;
00738 
00739     return (unsigned) _lastdev_tried < _devices.size() - 1;
00740   }
00741 
00742   void
00743   MediaCD::getDetectedDevices(std::vector<std::string> & devices,
00744                               unsigned int & index) const
00745   {
00746     index = 0;
00747     if (!devices.empty())
00748       devices.clear();
00749 
00750     for (DeviceList::const_iterator it = _devices.begin();
00751          it != _devices.end(); ++it)
00752       devices.push_back(it->name);
00753 
00754     if (_lastdev >= 0)
00755       index = _lastdev;
00756 
00757     // try to detect again if _devices are empty (maybe this method will be
00758     // called before _devices get actually filled)
00759     if (devices.empty())
00760     {
00761       DBG << "no device list so far, trying to detect" << endl;
00762 
00763       DeviceList detected(
00764         detectDevices(_url.getScheme() == "dvd" ? true : false));
00765 
00766       for (DeviceList::const_iterator it = detected.begin();
00767            it != detected.end(); ++it)
00768         devices.push_back(it->name);
00769 
00770       // don't know which one is in use in this case
00771       index = 0;
00772     }
00773 
00774     MIL << "got " << devices.size() << " detected devices, current: "
00775         << (index < devices.size() ? devices[index] : "<none>")
00776         << "(" << index << ")" << endl;
00777   }
00778 
00779 
00780   } // namespace media
00781 } // namespace zypp
00782 // vim: set ts=8 sts=2 sw=2 ai noet: