00001
00002
00003
00004
00005
00006
00007
00008
00012 #include <iostream>
00013 #include <fstream>
00014 #include "zypp/base/Logger.h"
00015 #include "zypp/base/Exception.h"
00016
00017 #include "zypp/PathInfo.h"
00018 #include "zypp/Digest.h"
00019 #include "zypp/CheckSum.h"
00020 #include "zypp/KeyRing.h"
00021
00022 #include "zypp/source/susetags/SuseTagsImpl.h"
00023 #include "zypp/source/susetags/PackagesParser.h"
00024 #include "zypp/source/susetags/PackagesLangParser.h"
00025 #include "zypp/source/susetags/SelectionTagFileParser.h"
00026 #include "zypp/source/susetags/PatternTagFileParser.h"
00027 #include "zypp/source/susetags/ProductMetadataParser.h"
00028
00029 #include "zypp/SourceFactory.h"
00030 #include "zypp/ZYppCallbacks.h"
00031 #include "zypp/ZYppFactory.h"
00032
00033 using std::endl;
00034
00036 namespace zypp
00037 {
00038
00039 namespace source
00040 {
00041
00042 namespace susetags
00043 {
00044
00046
00047
00048
00049
00050 SuseTagsImpl::SuseTagsImpl()
00051 {}
00052
00053 void SuseTagsImpl::initCacheDir(const Pathname & cache_dir_r)
00054 {
00055
00056 if (cache_dir_r == Pathname("/") )
00057 ZYPP_THROW(Exception("I refuse to use / as cache dir"));
00058
00059 if (0 != assert_dir(cache_dir_r.dirname(), 0700))
00060 ZYPP_THROW(Exception("Cannot create cache directory" + cache_dir_r.asString()));
00061
00062 filesystem::clean_dir(cache_dir_r);
00063 filesystem::mkdir(cache_dir_r + "DATA");
00064 filesystem::mkdir(cache_dir_r + "MEDIA");
00065
00066 filesystem::mkdir(cache_dir_r + "PUBLICKEYS");
00067 }
00068
00069 void SuseTagsImpl::storeMetadata(const Pathname & cache_dir_r)
00070 {
00071 _cache_dir = cache_dir_r;
00072
00073
00074
00075
00076 INT << "Source metadata store..." << cache_dir_r << endl;
00077
00078
00079 Pathname new_media_file = provideFile("media.1/media");
00080
00081
00082 Pathname cached_media_file = _cache_dir + "MEDIA/media.1/media";
00083 if ( cacheExists() )
00084 {
00085 CheckSum old_media_file_checksum( "SHA1", filesystem::sha1sum(cached_media_file));
00086 CheckSum new_media_file_checksum( "SHA1", filesystem::sha1sum(new_media_file));
00087 if ( (new_media_file_checksum == old_media_file_checksum) && (!new_media_file_checksum.empty()) && (! old_media_file_checksum.empty()))
00088 {
00089 MIL << "susetags source " << alias() << " has not changed. Refresh completed. SHA1 of media.1/media file is " << old_media_file_checksum.checksum() << std::endl;
00090 return;
00091 }
00092 }
00093 MIL << "susetags source " << alias() << " has changed. Re-reading metadata into " << cache_dir_r << endl;
00094
00095
00096
00097
00098
00099
00100 Pathname media_src = provideDirTree("media.1");
00101 Pathname descr_src = provideDirTree(_orig_descr_dir);
00102 Pathname content_src = provideFile( _path + "content");
00103
00104 initCacheDir(cache_dir_r);
00105
00106
00107 std::list<std::string> files;
00108 dirInfo( 1, files, _path);
00109
00110 if (0 != assert_dir((cache_dir_r + "DATA"), 0700))
00111 {
00112 ZYPP_THROW(Exception("Cannot create cache DATA directory: " + (cache_dir_r + "DATA").asString()));
00113 }
00114 else
00115 {
00116 filesystem::copy_dir(descr_src, cache_dir_r + "DATA");
00117 MIL << "cached descr directory" << std::endl;
00118 filesystem::copy(content_src, cache_dir_r + "DATA/content");
00119 MIL << "cached content file" << std::endl;
00120 }
00121
00122 if (0 != assert_dir((cache_dir_r + "PUBLICKEYS"), 0700))
00123 {
00124 ZYPP_THROW(Exception("Cannot create cache PUBLICKEYS directory: " + (cache_dir_r + "PUBLICKEYS").asString()));
00125 }
00126 else
00127 {
00128
00129 for( std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it)
00130 {
00131 std::string filename = *it;
00132 if ( filename.substr(0, 10) == "gpg-pubkey" )
00133 {
00134 Pathname key_src = provideFile(_path + filename);
00135 MIL << "Trying to cache " << key_src << std::endl;
00136 filesystem::copy(key_src, cache_dir_r + "PUBLICKEYS/" + filename);
00137 MIL << "cached " << filename << std::endl;
00138 }
00139 else if( (filename == "content.asc") || (filename == "content.key"))
00140 {
00141 Pathname src_data = provideFile(_path + filename);
00142 filesystem::copy( src_data, cache_dir_r + "DATA/" + filename);
00143 MIL << "cached " << filename << std::endl;
00144 }
00145 }
00146 }
00147
00148 if (0 != assert_dir((cache_dir_r + "MEDIA"), 0700))
00149 {
00150 ZYPP_THROW(Exception("Cannot create cache MEDIA directory: " + (cache_dir_r + "MEDIA").asString()));
00151 }
00152 else
00153 {
00154 filesystem::copy_dir(media_src, cache_dir_r + "MEDIA");
00155 MIL << "cached media directory" << std::endl;
00156 }
00157 }
00158
00159 bool SuseTagsImpl::cacheExists()
00160 {
00161 MIL << "Checking if source cache exists in "<< _cache_dir << std::endl;
00162 bool exists = true;
00163
00164 bool data_exists = PathInfo(_cache_dir + "DATA").isExist();
00165 exists = exists && data_exists;
00166
00167
00168
00169
00170 bool media_exists = PathInfo(_cache_dir + "MEDIA").isExist();
00171 exists = exists && media_exists;
00172
00173 bool media_file_exists = PathInfo(_cache_dir + "MEDIA/media.1/media").isExist();
00174 exists = exists && media_file_exists;
00175
00176 MIL << "DATA " << (data_exists ? "exists" : "not found") << ", MEDIA " << (media_exists ? "exists" : "not found") << ", MEDIA/media.1/media " << (media_file_exists ? "exists" : "not found") << std::endl;
00177 return exists;
00178 }
00179
00180 bool SuseTagsImpl::verifyChecksumsMode()
00181 {
00182 return ! _prodImpl->_descr_files_checksums.empty();
00183 }
00184
00185 void SuseTagsImpl::factoryInit()
00186 {
00187 media::MediaManager media_mgr;
00188
00189 std::string vendor;
00190 std::string media_id;
00191 bool cache = cacheExists();
00192
00193 try {
00194 Pathname media_file = Pathname("media.1/media");
00195
00196 if (cache)
00197 {
00198 media_file = _cache_dir + "MEDIA" + media_file;
00199 }
00200 else
00201 {
00202 media::MediaAccessId _media = _media_set->getMediaAccessId(1);
00203 media_mgr.provideFile (_media, media_file);
00204 media_file = media_mgr.localPath (_media, media_file);
00205 }
00206
00207 std::ifstream pfile( media_file.asString().c_str() );
00208
00209 if ( pfile.bad() ) {
00210 ERR << "Error parsing media.1/media from file" << media_file << endl;
00211 ZYPP_THROW(Exception("Error parsing media.1/media") );
00212 }
00213
00214 _vendor = str::getline( pfile, str::TRIM );
00215
00216 if ( pfile.fail() ) {
00217 ERR << "Error parsing media.1/media" << endl;
00218 ZYPP_THROW(Exception("Error parsing media.1/media") );
00219 }
00220
00221 _media_id = str::getline( pfile, str::TRIM );
00222
00223 if ( pfile.fail() ) {
00224 ERR << "Error parsing media.1/media" << endl;
00225 ZYPP_THROW(Exception("Error parsing media.1/media") );
00226 }
00227
00228 std::string media_count_str = str::getline( pfile, str::TRIM );
00229
00230 if ( pfile.fail() ) {
00231 ERR << "Error parsing media.1/media" << endl;
00232 ZYPP_THROW(Exception("Error parsing media.1/media") );
00233 }
00234
00235 _media_count = str::strtonum<unsigned>( media_count_str );
00236
00237 }
00238 catch ( const Exception & excpt_r )
00239 {
00240 ERR << "Cannot read /media.1/media file, cannot initialize source" << endl;
00241 ZYPP_THROW( Exception("Cannot read /media.1/media file, cannot initialize source") );
00242 }
00243
00244 try {
00245 MIL << "Adding susetags media verifier: " << endl;
00246 MIL << "Vendor: " << _vendor << endl;
00247 MIL << "Media ID: " << _media_id << endl;
00248
00249
00250 media::MediaAccessId _media = _media_set->getMediaAccessId(1, true);
00251 media_mgr.delVerifier(_media);
00252 media_mgr.addVerifier(_media, media::MediaVerifierRef(
00253 new SourceImpl::Verifier (_vendor, _media_id) ));
00254 }
00255 catch (const Exception & excpt_r)
00256 {
00257 #warning FIXME: If media data is not set, verifier is not set. Should the media
00258 ZYPP_CAUGHT(excpt_r);
00259 WAR << "Verifier not found" << endl;
00260 }
00261
00262 readContentFile();
00263 }
00264
00265 void SuseTagsImpl::createResolvables(Source_Ref source_r)
00266 {
00267 callback::SendReport<CreateSourceReport> report;
00268 report->startData( url() );
00269
00270 provideProducts ( source_r, _store );
00271 providePackages ( source_r, _store );
00272 provideSelections ( source_r, _store );
00273 providePatterns ( source_r, _store );
00274
00275 report->finishData( url(), CreateSourceReport::NO_ERROR, "" );
00276 }
00277
00278 const std::list<Pathname> SuseTagsImpl::publicKeys()
00279 {
00280 bool cache = cacheExists();
00281 std::list<std::string> files;
00282 std::list<Pathname> paths;
00283
00284 MIL << "Reading public keys..." << std::endl;
00285 if (cache)
00286 {
00287 filesystem::readdir(files, _cache_dir + "PUBLICKEYS");
00288 for( std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it)
00289 paths.push_back(Pathname(*it));
00290
00291 MIL << "read " << files.size() << " keys from cache " << _cache_dir << std::endl;
00292 }
00293 else
00294 {
00295 std::list<std::string> allfiles;
00296 dirInfo(1, allfiles, _path);
00297
00298 for( std::list<std::string>::const_iterator it = allfiles.begin(); it != allfiles.end(); ++it)
00299 {
00300 std::string filename = *it;
00301 if ( filename.substr(0, 10) == "gpg-pubkey" )
00302 {
00303 Pathname key_src = provideFile(_path + filename);
00304 paths.push_back(filename);
00305 }
00306 }
00307 MIL << "read " << paths.size() << " keys from media" << std::endl;
00308 }
00309 return paths;
00310 }
00311
00312 ResStore SuseTagsImpl::provideResolvables(Source_Ref source_r, Resolvable::Kind kind)
00313 {
00314 callback::SendReport<CreateSourceReport> report;
00315 report->startData( url() );
00316
00317 ResStore store;
00318
00319 if ( kind == ResTraits<Product>::kind )
00320 provideProducts ( source_r, store );
00321 else if ( kind == ResTraits<Package>::kind )
00322 providePackages ( source_r, store );
00323 else if ( kind == ResTraits<Selection>::kind )
00324 provideSelections ( source_r, store );
00325 else if ( kind == ResTraits<Pattern>::kind )
00326 providePatterns ( source_r, store );
00327
00328 report->finishData( url(), CreateSourceReport::NO_ERROR, "" );
00329
00330 return store;
00331 }
00332
00334
00335
00336
00337
00338 SuseTagsImpl::~SuseTagsImpl()
00339 {}
00340
00341 Pathname SuseTagsImpl::sourceDir( const std::string & dir )
00342 {
00343 return Pathname( _data_dir + Pathname( dir ) + "/");
00344 }
00345
00346 media::MediaVerifierRef SuseTagsImpl::verifier(media::MediaNr media_nr)
00347 {
00348 return media::MediaVerifierRef(
00349 new SourceImpl::Verifier (_vendor, _media_id, media_nr));
00350 }
00351
00352 unsigned SuseTagsImpl::numberOfMedia(void) const
00353 { return _media_count; }
00354
00355 std::string SuseTagsImpl::vendor (void) const
00356 { return _vendor; }
00357
00358 std::string SuseTagsImpl::unique_id (void) const
00359 { return _media_id; }
00360
00361 void SuseTagsImpl::readContentFile()
00362 {
00363 Pathname p;
00364 bool cache = cacheExists();
00365
00366 if ( cache )
00367 {
00368 DBG << "Cached metadata found. Reading from " << _cache_dir << endl;
00369 _content_file = _cache_dir + "DATA/content";
00370
00371 if (PathInfo(_cache_dir + "DATA/content.key").isExist())
00372 _content_file_key = _cache_dir + "DATA/content.key";
00373
00374 if (PathInfo(_cache_dir + "DATA/content.asc").isExist())
00375 _content_file_sig = _cache_dir + "DATA/content.asc";
00376 }
00377 else
00378 {
00379 DBG << "Cached metadata not found in [" << _cache_dir << "]. Reading from " << _path << endl;
00380
00381 _content_file = provideFile(_path + "content");
00382
00383
00384
00385 std::list<std::string> files;
00386 dirInfo( 1, files, _path);
00387
00388 for( std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it)
00389 {
00390 std::string filename = *it;
00391
00392
00393
00394 if (filename == "content.key")
00395 _content_file_key = provideFile( _path + "content.key");
00396 else if (filename == "content.asc")
00397 _content_file_sig = provideFile( _path + "content.asc");
00398
00399
00400 }
00401 }
00402
00403 ZYpp::Ptr z = getZYpp();
00404
00405 if ( PathInfo(_content_file_key).isExist() )
00406 {
00407
00408 z->keyRing()->importKey(_content_file_key, false);
00409 }
00410
00411
00412 std::list<Pathname> otherkeys = publicKeys();
00413 for ( std::list<Pathname>::const_iterator it = otherkeys.begin(); it != otherkeys.end(); ++it)
00414 {
00415 Pathname key = *it;
00416 z->keyRing()->importKey(key, false);
00417 }
00418
00419 MIL << "SuseTags source: checking 'content' file vailidity using digital signature.." << endl;
00420
00421 bool valid = z->keyRing()->verifyFileSignatureWorkflow( _content_file, (path() + "content").asString() + " (" + url().asString() + ")", _content_file_sig);
00422
00423
00424 if (!valid)
00425 ZYPP_THROW (Exception( "Error. Source signature does not validate and user does not want to continue. "));
00426
00427 SourceFactory factory;
00428 try {
00429 DBG << "Going to parse content file " << _content_file << endl;
00430
00431 ProductMetadataParser p;
00432 p.parse( _content_file, factory.createFrom(this) );
00433 _product = p.result;
00434
00435
00436 _data_dir = _path + p.prodImpl->_data_dir;
00437
00438
00439 _orig_descr_dir = _path + p.prodImpl->_description_dir;
00440
00441 if (cache)
00442 _descr_dir = _cache_dir + "DATA/descr";
00443 else
00444 _descr_dir = _orig_descr_dir;
00445
00446 MIL << "Read product: " << _product->summary() << endl;
00447
00448 _prodImpl = p.prodImpl;
00449 _autorefresh = p.volatile_content && media::MediaAccess::canBeVolatile( _url );
00450 }
00451 catch (Exception & excpt_r) {
00452 ZYPP_THROW (Exception( "cannot parse content file."));
00453 }
00454 }
00455
00456 void SuseTagsImpl::provideProducts(Source_Ref source_r, ResStore &store)
00457 {
00458 MIL << "Adding product: " << _product->summary() << " to the store" << endl;
00459 store.insert( _product );
00460 }
00461
00462 void SuseTagsImpl::verifyFile( const Pathname &path, const std::string &key)
00463 {
00464
00465 if ( verifyChecksumsMode() )
00466 {
00467 MIL << "Going to check " << path << " file checksum" << std::endl;
00468 std::ifstream file(path.asString().c_str());
00469 if (!file) {
00470 ZYPP_THROW (Exception( "Can't open " + path.asString() ) );
00471 }
00472
00473
00474 CheckSum checksum = _prodImpl->_descr_files_checksums[key];
00475 if (checksum.empty())
00476 {
00477 callback::SendReport<DigestReport> report;
00478
00479 if ( report->askUserToAcceptNoDigest(path) )
00480 {
00481 MIL << path << " user accepted file without a checksum " << endl;
00482 return;
00483 }
00484
00485 ZYPP_THROW (Exception( "Error, Missing checksum for " + path.asString() ) );
00486 }
00487 else
00488 {
00489 std::string found_checksum = Digest::digest( checksum.type(), file);
00490 if ( found_checksum != checksum.checksum() )
00491 {
00492 ZYPP_THROW (Exception( "Corrupt source, Expected " + checksum.type() + " " + checksum.checksum() + ", got " + found_checksum + " for " + path.asString() ) );
00493 }
00494 else
00495 {
00496 MIL << path << " checksum ok (" << checksum.type() << " " << checksum.checksum() << ")" << std::endl;
00497 return;
00498 }
00499 }
00500 }
00501 }
00502
00503 void SuseTagsImpl::providePackages(Source_Ref source_r, ResStore &store)
00504 {
00505 bool cache = cacheExists();
00506
00507 Pathname p = cache ? _descr_dir + "packages" : provideFile( _descr_dir + "packages");
00508
00509 verifyFile( p, "packages");
00510
00511 DBG << "Going to parse " << p << endl;
00512 PkgContent content( parsePackages( source_r, this, p ) );
00513
00514 #warning Should use correct locale and locale fallback list
00515
00516
00517
00518
00519
00520 ZYpp::Ptr z = getZYpp();
00521 Locale lang( z->getTextLocale() );
00522
00523 std::string packages_lang_prefix( "packages." );
00524 std::string packages_lang_name;
00525
00526
00527 std::list<std::string> all_files;
00528 if (cache)
00529 filesystem::readdir(all_files, _descr_dir);
00530 else
00531 dirInfo(1, all_files, _descr_dir);
00532
00533 std::list<std::string> _pkg_translations;
00534 for( std::list<std::string>::const_iterator it = all_files.begin(); it != all_files.end(); ++it)
00535 {
00536 if ( ((*it).substr(0, 9) == "packages." ) && ((*it) != "packages.DU" ))
00537 {
00538 MIL << *it << " available as package data translation." << std::endl;
00539 _pkg_translations.push_back(*it);
00540 }
00541 }
00542
00543
00544 bool trymore = true;
00545 while ( (lang != Locale()) && trymore )
00546 {
00547 packages_lang_name = packages_lang_prefix + lang.code();
00548 MIL << "Translation candidate: " << lang.code() << std::endl;
00549 try
00550 {
00551
00552 if ( find( _pkg_translations.begin(), _pkg_translations.end(), packages_lang_name ) != _pkg_translations.end() )
00553 {
00554 p = cache ? _descr_dir + packages_lang_name : provideFile( _descr_dir + packages_lang_name);
00555 if ( PathInfo(p).isExist() )
00556 {
00557 MIL << packages_lang_name << " found" << std::endl;
00558 DBG << "Going to parse " << p << endl;
00559 verifyFile( p, packages_lang_name);
00560 parsePackagesLang( this, p, lang, content );
00561 trymore = false;
00562 }
00563 else
00564 {
00565 ERR << packages_lang_name << " can't be provided, even if it exist in the media" << endl;
00566 }
00567 }
00568 else
00569 {
00570 MIL << "Skipping translation candidate " << packages_lang_name << " (not present in media)" << endl;
00571 }
00572 }
00573 catch (Exception & excpt_r)
00574 {
00575 ZYPP_CAUGHT(excpt_r);
00576 }
00577 lang = lang.fallback();
00578 }
00579
00580 MIL << _package_data.size() << " packages holding real data" << std::endl;
00581 MIL << content.size() << " packages parsed" << std::endl;
00582
00583 int counter =0;
00584 for ( std::map<NVRA,bool>::const_iterator it = _is_shared.begin(); it != _is_shared.end(); ++it)
00585 {
00586 if( it->second)
00587 counter++;
00588 }
00589
00590 MIL << counter << " packages sharing data" << std::endl;
00591
00592
00593 PkgDiskUsage du;
00594 try
00595 {
00596 p = cache ? _descr_dir + "packages.DU" : provideFile( _descr_dir + "packages.DU");
00597 verifyFile( p, "packages.DU");
00598 du = parsePackagesDiskUsage(p);
00599 }
00600 catch (Exception & excpt_r)
00601 {
00602 WAR << "Problem trying to parse the disk usage info" << endl;
00603 }
00604
00605 for (PkgContent::const_iterator it = content.begin(); it != content.end(); ++it)
00606 {
00607 it->second->_diskusage = du[it->first ];
00608 Package::Ptr pkg = detail::makeResolvableFromImpl( it->first, it->second );
00609 store.insert( pkg );
00610
00611
00612
00613
00614
00615 }
00616 DBG << "SuseTagsImpl (fake) from " << p << ": "
00617 << content.size() << " packages" << endl;
00618 }
00619
00620 void SuseTagsImpl::provideSelections(Source_Ref source_r, ResStore &store)
00621 {
00622 bool cache = cacheExists();
00623
00624 Pathname p;
00625
00626 bool file_found = true;
00627
00628
00629 try {
00630 p = cache ? _descr_dir + "selections" : provideFile( _descr_dir + "selections");
00631 if ( cache && ( ! PathInfo(p).isExist()) )
00632 {
00633 MIL << p << " not found." << endl;
00634 file_found = false;
00635 }
00636 }
00637 catch (Exception & excpt_r)
00638 {
00639 MIL << "Cannot provide file 'selections'" << endl;
00640 file_found = false;
00641 }
00642
00643 if (file_found)
00644 {
00645 verifyFile( p, "selections");
00646
00647 std::ifstream sels (p.asString().c_str());
00648
00649 while (sels && !sels.eof())
00650 {
00651 std::string selfile;
00652 getline(sels,selfile);
00653
00654 if (selfile.empty() ) continue;
00655 DBG << "Going to parse selection " << selfile << endl;
00656
00657 Pathname file = cache ? _descr_dir + selfile : provideFile( _descr_dir + selfile);
00658 verifyFile( file, selfile);
00659
00660 MIL << "Selection file to parse " << file << endl;
00661 Selection::Ptr sel( parseSelection( source_r, file ) );
00662
00663 if (sel)
00664 {
00665 DBG << "Selection:" << sel << endl;
00666 store.insert( sel );
00667 DBG << "Parsing of " << file << " done" << endl;
00668 }
00669 else
00670 {
00671 DBG << "Parsing of " << file << " failed" << endl;
00672 }
00673
00674
00675 }
00676 }
00677 }
00678
00679 void SuseTagsImpl::providePatterns(Source_Ref source_r, ResStore &store)
00680 {
00681 bool cache = cacheExists();
00682 Pathname p;
00683
00684
00685 bool file_found = true;
00686
00687 try {
00688 p = cache ? _descr_dir + "patterns" : provideFile( _descr_dir + "patterns");
00689 if ( cache && ( ! PathInfo(p).isExist()) )
00690 {
00691 MIL << p << " not found." << endl;
00692 file_found = false;
00693 }
00694 }
00695 catch (Exception & excpt_r)
00696 {
00697 MIL << "'patterns' file not found" << endl;
00698 file_found = false;
00699 }
00700
00701 if ( file_found )
00702 {
00703 verifyFile( p, "patterns");
00704 std::ifstream pats (p.asString().c_str());
00705
00706 while (pats && !pats.eof())
00707 {
00708 std::string patfile;
00709 getline(pats,patfile);
00710
00711 if (patfile.empty() ) continue;
00712
00713 DBG << "Going to parse pattern " << patfile << endl;
00714
00715 Pathname file = cache ? _descr_dir + patfile : provideFile( _descr_dir + patfile);
00716 verifyFile( file, patfile);
00717
00718 MIL << "Pattern file to parse " << file << endl;
00719 Pattern::Ptr pat( parsePattern( source_r, file ) );
00720
00721 if (pat)
00722 {
00723 DBG << "Pattern:" << pat << endl;
00724 _store.insert( pat );
00725 DBG << "Parsing of " << file << " done" << endl;
00726 }
00727 else
00728 {
00729 DBG << "Parsing of " << file << " failed" << endl;
00730 }
00731 }
00732 }
00733 }
00734
00736
00737
00738
00739
00740 std::ostream & SuseTagsImpl::dumpOn( std::ostream & str ) const
00741 {
00742 return SourceImpl::dumpOn( str );
00743 }
00744
00746 }
00749 }
00752 }