libzypp 8.13.6
|
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: