Solvable.cc

Go to the documentation of this file.
00001 /*---------------------------------------------------------------------\
00002 |                          ____ _   __ __ ___                          |
00003 |                         |__  / \ / / . \ . \                         |
00004 |                           / / \ V /|  _/  _/                         |
00005 |                          / /__ | | | | | |                           |
00006 |                         /_____||_| |_| |_|                           |
00007 |                                                                      |
00008 \---------------------------------------------------------------------*/
00012 #include <iostream>
00013 
00014 #include "zypp/base/Logger.h"
00015 #include "zypp/base/Gettext.h"
00016 #include "zypp/base/Exception.h"
00017 #include "zypp/base/Functional.h"
00018 #include "zypp/base/Collector.h"
00019 
00020 #include "zypp/sat/detail/PoolImpl.h"
00021 #include "zypp/sat/Solvable.h"
00022 #include "zypp/sat/Pool.h"
00023 #include "zypp/sat/LookupAttr.h"
00024 
00025 #include "zypp/Repository.h"
00026 #include "zypp/OnMediaLocation.h"
00027 #include "zypp/ZConfig.h"
00028 
00029 using std::endl;
00030 
00032 namespace zypp
00033 { 
00034 
00035   namespace sat
00036   { 
00037 
00038     namespace
00039     {
00040       void _doSplit( IdString & _ident, ResKind & _kind, IdString & _name )
00041       {
00042         if ( ! _ident )
00043           return;
00044 
00045         ResKind explicitKind = Solvable::SplitIdent::explicitKind( _ident.c_str() );
00046         // NOTE: kind package and srcpackage do not have namespaced ident!
00047         if ( ! explicitKind  )
00048         {
00049           _name = _ident;
00050           // No kind defaults to package
00051           if ( !_kind )
00052             _kind = ResKind::package;
00053           if ( ! ( _kind == ResKind::package || _kind == ResKind::srcpackage ) )
00054             _ident = IdString( str::form( "%s:%s", _kind.c_str(), _ident.c_str() ) );
00055         }
00056         else
00057         {
00058           // strip kind spec from name
00059           _name = IdString( ::strchr( _ident.c_str(), ':' )+1 );
00060           _kind = explicitKind;
00061           if ( _kind == ResKind::package || _kind == ResKind::srcpackage )
00062             _ident = _name;
00063         }
00064         return;
00065       }
00066     }
00067 
00068     Solvable::SplitIdent::SplitIdent( IdString ident_r )
00069     : _ident( ident_r )
00070     { _doSplit( _ident, _kind, _name ); }
00071 
00072     Solvable::SplitIdent::SplitIdent( const char * ident_r )
00073     : _ident( ident_r )
00074     { _doSplit( _ident, _kind, _name ); }
00075 
00076     Solvable::SplitIdent::SplitIdent( const std::string & ident_r )
00077     : _ident( ident_r )
00078     { _doSplit( _ident, _kind, _name ); }
00079 
00080     Solvable::SplitIdent::SplitIdent( ResKind kind_r, IdString name_r )
00081     : _ident( name_r )
00082     , _kind( kind_r )
00083     { _doSplit( _ident, _kind, _name ); }
00084 
00085     Solvable::SplitIdent::SplitIdent( ResKind kind_r, const C_Str & name_r )
00086     : _ident( name_r )
00087     , _kind( kind_r )
00088     { _doSplit( _ident, _kind, _name ); }
00089 
00090     ResKind Solvable::SplitIdent::explicitKind( const char * ident_r )
00091     {
00092       if ( ! ident_r )
00093         return ResKind();
00094 
00095       const char * sep = ::strchr( ident_r, ':' );
00096       if ( ! sep )
00097         return ResKind();
00098 
00099       ResKind ret;
00100       if ( sep-ident_r >= 4 )
00101       {
00102         switch ( ident_r[3] )
00103         {
00104           #define OUTS(K,S) if ( !::strncmp( ident_r, ResKind::K.c_str(), S ) && ident_r[S] == ':' ) ret = ResKind::K
00105           //             ----v
00106           case 'c': OUTS( patch, 5 );       break;
00107           case 'd': OUTS( product, 7 );     break;
00108           case 'k': OUTS( package, 7 );     break;
00109           case 'p': OUTS( srcpackage, 10 ); break;
00110           case 't': OUTS( pattern, 7 );     break;
00111           #undef OUTS
00112         }
00113       }
00114       return ret;
00115     }
00116 
00118 
00119     const Solvable Solvable::noSolvable;
00120 
00122 
00123     ::_Solvable * Solvable::get() const
00124     { return myPool().getSolvable( _id ); }
00125 
00126 #define NO_SOLVABLE_RETURN( VAL ) \
00127     ::_Solvable * _solvable( get() ); \
00128     if ( ! _solvable ) return VAL
00129 
00130     Solvable Solvable::nextInPool() const
00131     { return Solvable( myPool().getNextId( _id ) ); }
00132 
00133     Solvable Solvable::nextInRepo() const
00134     {
00135       NO_SOLVABLE_RETURN( noSolvable );
00136       for ( detail::SolvableIdType next = _id+1; next < unsigned(_solvable->repo->end); ++next )
00137       {
00138         ::_Solvable * nextS( myPool().getSolvable( next ) );
00139         if ( nextS && nextS->repo == _solvable->repo )
00140         {
00141           return Solvable( next );
00142         }
00143       }
00144       return noSolvable;
00145     }
00146 
00147     Repository Solvable::repository() const
00148     {
00149       NO_SOLVABLE_RETURN( Repository::noRepository );
00150       return Repository( _solvable->repo );
00151     }
00152 
00153     bool Solvable::isSystem() const
00154     {
00155       NO_SOLVABLE_RETURN( _id == detail::systemSolvableId );
00156       return myPool().isSystemRepo( _solvable->repo );
00157     }
00158 
00159     IdString Solvable::ident() const
00160     {
00161       NO_SOLVABLE_RETURN( IdString() );
00162       return IdString( _solvable->name );
00163     }
00164 
00165     std::string Solvable::lookupStrAttribute( const SolvAttr & attr ) const
00166     {
00167       NO_SOLVABLE_RETURN( std::string() );
00168       const char * s = ::solvable_lookup_str( _solvable, attr.id() );
00169       return s ? s : std::string();
00170     }
00171 
00172     std::string Solvable::lookupStrAttribute( const SolvAttr & attr, const Locale & lang_r ) const
00173     {
00174       NO_SOLVABLE_RETURN( std::string() );
00175       const char * s = 0;
00176       if ( lang_r == Locale::noCode )
00177       {
00178         s = ::solvable_lookup_str_poollang( _solvable, attr.id() );
00179       }
00180       else
00181       {
00182         for ( Locale l( lang_r ); l != Locale::noCode; l = l.fallback() )
00183           if ( (s = ::solvable_lookup_str_lang( _solvable, attr.id(), l.code().c_str(), 0 )) )
00184             return s;
00185           // here: no matching locale, so use default
00186           s = ::solvable_lookup_str_lang( _solvable, attr.id(), 0, 0 );
00187       }
00188       return s ? s : std::string();
00189    }
00190 
00191     unsigned Solvable::lookupNumAttribute( const SolvAttr & attr ) const
00192     {
00193       NO_SOLVABLE_RETURN( 0 );
00194       return ::solvable_lookup_num( _solvable, attr.id(), 0 );
00195     }
00196 
00197     bool Solvable::lookupBoolAttribute( const SolvAttr & attr ) const
00198     {
00199       NO_SOLVABLE_RETURN( false );
00200       return ::solvable_lookup_bool( _solvable, attr.id() );
00201     }
00202 
00203     detail::IdType Solvable::lookupIdAttribute( const SolvAttr & attr ) const
00204     {
00205       NO_SOLVABLE_RETURN( detail::noId );
00206       return ::solvable_lookup_id( _solvable, attr.id() );
00207     }
00208 
00209     CheckSum Solvable::lookupCheckSumAttribute( const SolvAttr & attr ) const
00210     {
00211       NO_SOLVABLE_RETURN( CheckSum() );
00212       detail::IdType chksumtype = 0;
00213       const char * s = ::solvable_lookup_checksum( _solvable, attr.id(), &chksumtype );
00214       if ( ! s )
00215         return CheckSum();
00216       switch ( chksumtype )
00217       {
00218         case REPOKEY_TYPE_MD5:    return CheckSum::md5( s );
00219         case REPOKEY_TYPE_SHA1:   return CheckSum::sha1( s );
00220         case REPOKEY_TYPE_SHA256: return CheckSum::sha256( s );
00221       }
00222       return CheckSum( std::string(), s ); // try to autodetect
00223     }
00224 
00226     namespace
00227     {
00228       inline Pathname lookupDatadirIn( Repository repor_r )
00229       {
00230         static const sat::SolvAttr susetagsDatadir( "susetags:datadir" );
00231         Pathname ret;
00232         // First look for repo attribute "susetags:datadir". If not found,
00233         // look into the solvables as Code11 satsolver placed it there.
00234         sat::LookupRepoAttr datadir( susetagsDatadir, repor_r );
00235         if ( ! datadir.empty() )
00236           ret = datadir.begin().asString();
00237         else
00238         {
00239           sat::LookupAttr datadir( susetagsDatadir, repor_r );
00240           if ( ! datadir.empty() )
00241             ret = datadir.begin().asString();
00242         }
00243         return ret;
00244       }
00245     }
00247 
00248     OnMediaLocation Solvable::lookupLocation() const
00249     {
00250       NO_SOLVABLE_RETURN( OnMediaLocation() );
00251       // medianumber and path
00252       unsigned medianr;
00253       char * file = ::solvable_get_location( _solvable, &medianr );
00254       if ( ! file )
00255         return OnMediaLocation();
00256 
00257       OnMediaLocation ret;
00258 
00259       Pathname path;
00260       switch ( repository().info().type().toEnum() )
00261       {
00262         case repo::RepoType::NONE_e:
00263         {
00264           path = lookupDatadirIn( repository() );
00265           if ( ! path.empty() )
00266             repository().info().setProbedType( repo::RepoType::YAST2_e );
00267         }
00268         break;
00269 
00270         case repo::RepoType::YAST2_e:
00271         {
00272           path = lookupDatadirIn( repository() );
00273           if ( path.empty() )
00274             path = "suse";
00275         }
00276         break;
00277 
00278         default:
00279           break;
00280       }
00281       ret.setLocation    ( path/file, medianr );
00282       ret.setDownloadSize( ByteCount( lookupNumAttribute( SolvAttr::downloadsize ), ByteCount::K ) );
00283       ret.setChecksum    ( lookupCheckSumAttribute( SolvAttr::checksum ) );
00284       // Not needed/available for solvables?
00285       //ret.setOpenSize    ( ByteCount( lookupNumAttribute( SolvAttr::opensize ), ByteCount::K ) );
00286       //ret.setOpenChecksum( lookupCheckSumAttribute( SolvAttr::openchecksum ) );
00287       return ret;
00288     }
00289 
00290     ResKind Solvable::kind() const
00291     {
00292       NO_SOLVABLE_RETURN( ResKind() );
00293       // detect srcpackages by 'arch'
00294       switch ( _solvable->arch )
00295       {
00296         case ARCH_SRC:
00297         case ARCH_NOSRC:
00298           return ResKind::srcpackage;
00299           break;
00300       }
00301 
00302       const char * ident = IdString( _solvable->name ).c_str();
00303       const char * sep = ::strchr( ident, ':' );
00304 
00305       // no ':' in package names (hopefully)
00306       if ( ! sep )
00307         return ResKind::package;
00308 
00309       // quick check for well known kinds
00310       if ( sep-ident >= 4 )
00311       {
00312         switch ( ident[3] )
00313         {
00314 #define OUTS(K,S) if ( !::strncmp( ident, ResKind::K.c_str(), S ) ) return ResKind::K
00315           //             ----v
00316           case 'c': OUTS( patch, 5 );       break;
00317           case 'd': OUTS( product, 7 );     break;
00318           case 'k': OUTS( package, 7 );     break;
00319           case 'p': OUTS( srcpackage, 10 ); break;
00320           case 't': OUTS( pattern, 7 );     break;
00321 #undef OUTS
00322         }
00323       }
00324 
00325       // an unknown kind
00326       return ResKind( std::string( ident, sep-ident ) );
00327     }
00328 
00329     bool Solvable::isKind( const ResKind & kind_r ) const
00330     {
00331       NO_SOLVABLE_RETURN( false );
00332 
00333       // detect srcpackages by 'arch'
00334       switch ( _solvable->arch )
00335       {
00336         case ARCH_SRC:
00337         case ARCH_NOSRC:
00338           return( kind_r == ResKind::srcpackage );
00339           break;
00340       }
00341 
00342       // no ':' in package names (hopefully)
00343       const char * ident = IdString( _solvable->name ).c_str();
00344       if ( kind_r == ResKind::package )
00345       {
00346         return( ::strchr( ident, ':' ) == 0 );
00347       }
00348 
00349       // look for a 'kind:' prefix
00350       const char * kind = kind_r.c_str();
00351       unsigned     ksize = ::strlen( kind );
00352       return( ::strncmp( ident, kind, ksize ) == 0
00353               && ident[ksize] == ':' );
00354     }
00355 
00356     std::string Solvable::name() const
00357     {
00358       NO_SOLVABLE_RETURN( std::string() );
00359       const char * ident = IdString( _solvable->name ).c_str();
00360       const char * sep = ::strchr( ident, ':' );
00361       return( sep ? sep+1 : ident );
00362     }
00363 
00364     Edition Solvable::edition() const
00365     {
00366       NO_SOLVABLE_RETURN( Edition() );
00367       return Edition( _solvable->evr );
00368     }
00369 
00370     Arch Solvable::arch() const
00371     {
00372       NO_SOLVABLE_RETURN( Arch_noarch ); //ArchId() );
00373       switch ( _solvable->arch )
00374       {
00375         case ARCH_SRC:
00376         case ARCH_NOSRC:
00377           return Arch_noarch; //ArchId( ARCH_NOARCH );
00378           break;
00379       }
00380       return Arch( IdString(_solvable->arch).asString() );
00381       //return ArchId( _solvable->arch );
00382     }
00383 
00384     bool Solvable::multiversionInstall() const
00385     {
00386       return myPool().isMultiversion( ident() );
00387     }
00388 
00389     bool Solvable::installOnly() const { return multiversionInstall(); }
00390 
00391     IdString Solvable::vendor() const
00392     {
00393       NO_SOLVABLE_RETURN( IdString() );
00394       return IdString( _solvable->vendor );
00395     }
00396 
00397     Capabilities Solvable::operator[]( Dep which_r ) const
00398     {
00399       switch( which_r.inSwitch() )
00400       {
00401         case Dep::PROVIDES_e:    return provides();    break;
00402         case Dep::REQUIRES_e:    return requires();    break;
00403         case Dep::CONFLICTS_e:   return conflicts();   break;
00404         case Dep::OBSOLETES_e:   return obsoletes();   break;
00405         case Dep::RECOMMENDS_e:  return recommends();  break;
00406         case Dep::SUGGESTS_e:    return suggests();    break;
00407         case Dep::ENHANCES_e:    return enhances();    break;
00408         case Dep::SUPPLEMENTS_e: return supplements(); break;
00409         case Dep::PREREQUIRES_e: return prerequires(); break;
00410       }
00411       return Capabilities();
00412     }
00413 
00414     inline Capabilities _getCapabilities( detail::IdType * idarraydata_r, ::Offset offs_r )
00415     {
00416       return offs_r ? Capabilities( idarraydata_r + offs_r ) : Capabilities();
00417     }
00418     Capabilities Solvable::provides() const
00419     {
00420       NO_SOLVABLE_RETURN( Capabilities() );
00421       return _getCapabilities( _solvable->repo->idarraydata, _solvable->provides );
00422     }
00423     Capabilities Solvable::requires() const
00424     {
00425       NO_SOLVABLE_RETURN( Capabilities() );
00426       return _getCapabilities( _solvable->repo->idarraydata, _solvable->requires );
00427     }
00428     Capabilities Solvable::conflicts() const
00429     {
00430       NO_SOLVABLE_RETURN( Capabilities() );
00431       return _getCapabilities( _solvable->repo->idarraydata, _solvable->conflicts );
00432     }
00433     Capabilities Solvable::obsoletes() const
00434     {
00435       NO_SOLVABLE_RETURN( Capabilities() );
00436       return _getCapabilities( _solvable->repo->idarraydata, _solvable->obsoletes );
00437     }
00438     Capabilities Solvable::recommends() const
00439     {
00440       NO_SOLVABLE_RETURN( Capabilities() );
00441       return _getCapabilities( _solvable->repo->idarraydata, _solvable->recommends );
00442     }
00443     Capabilities Solvable::suggests() const
00444     {
00445       NO_SOLVABLE_RETURN( Capabilities() );
00446       return _getCapabilities( _solvable->repo->idarraydata, _solvable->suggests );
00447     }
00448     Capabilities Solvable::enhances() const
00449     {
00450       NO_SOLVABLE_RETURN( Capabilities() );
00451       return _getCapabilities( _solvable->repo->idarraydata, _solvable->enhances );
00452     }
00453     Capabilities Solvable::supplements() const
00454     {
00455       NO_SOLVABLE_RETURN( Capabilities() );
00456       return _getCapabilities( _solvable->repo->idarraydata, _solvable->supplements );
00457     }
00458     Capabilities Solvable::prerequires() const
00459     {
00460       NO_SOLVABLE_RETURN( Capabilities() );
00461       // prerequires are a subset of requires
00462        ::Offset offs = _solvable->requires;
00463        return offs ? Capabilities( _solvable->repo->idarraydata + offs, detail::solvablePrereqMarker )
00464                    : Capabilities();
00465     }
00466 
00467     CapabilitySet Solvable::providesNamespace( const std::string & namespace_r ) const
00468     {
00469       NO_SOLVABLE_RETURN( CapabilitySet() );
00470       CapabilitySet ret;
00471       Capabilities caps( provides() );
00472       for_( it, caps.begin(), caps.end() )
00473       {
00474         CapDetail caprep( it->detail() );
00475         if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' )
00476           ret.insert( *it );
00477       }
00478       return ret;
00479    }
00480 
00481     CapabilitySet Solvable::valuesOfNamespace( const std::string & namespace_r ) const
00482     {
00483       NO_SOLVABLE_RETURN( CapabilitySet() );
00484       CapabilitySet ret;
00485       Capabilities caps( provides() );
00486       for_( it, caps.begin(), caps.end() )
00487       {
00488         CapDetail caprep( it->detail() );
00489         if ( str::hasPrefix( caprep.name().c_str(), namespace_r ) && *(caprep.name().c_str()+namespace_r.size()) == '(' )
00490         {
00491           std::string value( caprep.name().c_str()+namespace_r.size()+1 );
00492           value[value.size()-1] = '\0'; // erase the trailing ')'
00493           ret.insert( Capability( value, caprep.op(), caprep.ed() ) );
00494         }
00495       }
00496       return ret;
00497     }
00498 
00499 
00500     std::string Solvable::asString() const
00501     {
00502       NO_SOLVABLE_RETURN( (_id == detail::systemSolvableId ? "systemSolvable" : "noSolvable") );
00503       return str::form( "%s-%s.%s",
00504                         IdString( _solvable->name ).c_str(),
00505                         IdString( _solvable->evr ).c_str(),
00506                         IdString( _solvable->arch ).c_str() );
00507     }
00508 
00509     bool Solvable::identical( Solvable rhs ) const
00510     {
00511       NO_SOLVABLE_RETURN( ! rhs.get() );
00512       ::_Solvable * rhssolvable( rhs.get() );
00513       return rhssolvable && ( _solvable == rhssolvable || ::solvable_identical( _solvable, rhssolvable ) );
00514     }
00515 
00517     namespace
00518     { 
00519 
00523       int invokeOnEachSupportedLocale( Capability cap_r, function<bool (const Locale &)> fnc_r )
00524       {
00525         CapDetail detail( cap_r );
00526         if ( detail.kind() == CapDetail::EXPRESSION )
00527         {
00528           switch ( detail.capRel() )
00529           {
00530             case CapDetail::CAP_AND:
00531             case CapDetail::CAP_OR:
00532                 // expand
00533               {
00534                 int res = invokeOnEachSupportedLocale( detail.lhs(), fnc_r );
00535                 if ( res < 0 )
00536                   return res; // negative on abort.
00537                 int res2 = invokeOnEachSupportedLocale( detail.rhs(), fnc_r );
00538                 if ( res2 < 0 )
00539                   return -res + res2; // negative on abort.
00540                 return res + res2;
00541               }
00542               break;
00543 
00544             case CapDetail::CAP_NAMESPACE:
00545               if ( detail.lhs().id() == NAMESPACE_LANGUAGE )
00546               {
00547                 return ( !fnc_r || fnc_r( Locale( IdString(detail.rhs().id()) ) ) ) ? 1 : -1; // negative on abort.
00548               }
00549               break;
00550 
00551             case CapDetail::REL_NONE:
00552             case CapDetail::CAP_WITH:
00553             case CapDetail::CAP_ARCH:
00554               break; // unwanted
00555           }
00556         }
00557         return 0;
00558       }
00559 
00564       inline int invokeOnEachSupportedLocale( Capabilities cap_r, function<bool (const Locale &)> fnc_r )
00565       {
00566         int cnt = 0;
00567         for_( cit, cap_r.begin(), cap_r.end() )
00568         {
00569           int res = invokeOnEachSupportedLocale( *cit, fnc_r );
00570           if ( res < 0 )
00571             return -cnt + res; // negative on abort.
00572           cnt += res;
00573         }
00574         return cnt;
00575       }
00577 
00578       // Functor returning false if a Locale is in the set.
00579       struct NoMatchIn
00580       {
00581         NoMatchIn( const LocaleSet & locales_r ) : _locales( locales_r ) {}
00582 
00583         bool operator()( const Locale & locale_r ) const
00584         {
00585           return _locales.find( locale_r ) == _locales.end();
00586         }
00587 
00588         const LocaleSet & _locales;
00589       };
00590 
00591     } 
00592 
00593     bool Solvable::supportsLocales() const
00594     {
00595       // false_c stops on 1st Locale.
00596       return invokeOnEachSupportedLocale( supplements(), functor::false_c() ) < 0;
00597     }
00598 
00599     bool Solvable::supportsLocale( const Locale & locale_r ) const
00600     {
00601       // not_equal_to stops on == Locale.
00602       return invokeOnEachSupportedLocale( supplements(), bind( std::not_equal_to<Locale>(), locale_r, _1 ) ) < 0;
00603     }
00604 
00605     bool Solvable::supportsLocale( const LocaleSet & locales_r ) const
00606     {
00607       if ( locales_r.empty() )
00608         return false;
00609       // NoMatchIn stops if Locale is included.
00610       return invokeOnEachSupportedLocale( supplements(), NoMatchIn(locales_r) ) < 0;
00611     }
00612 
00613     bool Solvable::supportsRequestedLocales() const
00614     { return supportsLocale( myPool().getRequestedLocales() ); }
00615 
00616     void Solvable::getSupportedLocales( LocaleSet & locales_r ) const
00617     {
00618       invokeOnEachSupportedLocale( supplements(),
00619                                    functor::Collector( std::inserter( locales_r, locales_r.begin() ) ) );
00620     }
00621 
00622     /******************************************************************
00623     **
00624     **  FUNCTION NAME : operator<<
00625     **  FUNCTION TYPE : std::ostream &
00626     */
00627     std::ostream & operator<<( std::ostream & str, const Solvable & obj )
00628     {
00629       if ( ! obj )
00630         return str << (obj.isSystem() ? "systemSolvable" : "noSolvable" );
00631 
00632       return str << "(" << obj.id() << ")"
00633           << ( obj.isKind( ResKind::srcpackage ) ? "srcpackage:" : "" ) << obj.ident()
00634           << '-' << obj.edition() << '.' << obj.arch() << "("
00635           << obj.repository().alias() << ")";
00636     }
00637 
00638     /******************************************************************
00639     **
00640     **  FUNCTION NAME : dumpOn
00641     **  FUNCTION TYPE : std::ostream &
00642     */
00643     std::ostream & dumpOn( std::ostream & str, const Solvable & obj )
00644     {
00645       str << obj;
00646       if ( obj )
00647       {
00648 #define OUTS(X) if ( ! obj[Dep::X].empty() ) str << endl << " " #X " " << obj[Dep::X]
00649         OUTS(PROVIDES);
00650         OUTS(PREREQUIRES);
00651         OUTS(REQUIRES);
00652         OUTS(CONFLICTS);
00653         OUTS(OBSOLETES);
00654         OUTS(RECOMMENDS);
00655         OUTS(SUGGESTS);
00656         OUTS(ENHANCES);
00657         OUTS(SUPPLEMENTS);
00658 #undef OUTS
00659       }
00660       return str;
00661     }
00662 
00664   } // namespace sat
00667 } // namespace zypp

doxygen