28 #include <boost/format.hpp>
51 using namespace zypp::filesystem;
53 #define WARNINGMAILPATH "/var/log/YaST2/"
54 #define FILEFORBACKUPFILES "YaSTBackupModifiedFiles"
55 #define MAXRPMMESSAGELINES 10000
59 namespace zypp_readonly_hack
69 #if 1 // No more need to escape whitespace since rpm-4.4.2.3
70 const char* quoteInFilename_m =
"\'\"";
72 const char* quoteInFilename_m =
" \t\'\"";
74 inline string rpmQuoteFilename(
const Pathname & path_r )
76 string path( path_r.asString() );
79 pos = path.find_first_of( quoteInFilename_m, pos ) )
81 path.insert( pos,
"\\" );
102 MIL <<
"trusted key added to zypp Keyring. Importing" << endl;
106 _rpmdb.importPubkey( key );
110 ERR <<
"Could not import key " << key.
id() <<
" (" << key.
name() <<
" from " << key.
path() <<
" in rpm database" << endl;
116 MIL <<
"Trusted key removed from zypp Keyring. Removing..." << endl;
121 _rpmdb.removePubkey( key );
125 ERR <<
"Could not remove key " << key.
id() <<
" (" << key.
name() <<
") from rpm database" << endl;
134 unsigned diffFiles(
const string file1,
const string file2,
string& out,
int maxlines)
144 ExternalProgram prog(argv,ExternalProgram::Discard_Stderr,
false, -1,
true);
155 if (maxlines<0?
true:count<maxlines)
170 inline string stringPath(
const Pathname & root_r,
const Pathname & sub_r )
183 if ( obj == RpmDb::DbSI_NO_INIT )
189 #define ENUM_OUT(B,C) str << ( obj & RpmDb::B ? C : '-' )
212 #define FAILIFNOTINITIALIZED if( ! initialized() ) { ZYPP_THROW(RpmDbNotOpenException()); }
223 : _dbStateInfo( DbSI_NO_INIT )
224 #warning Check for obsolete memebers
225 , _backuppath (
"/var/adm/backup")
226 , _packagebackups(false)
227 , _warndirexists(false)
234 setenv(
"RPM_IgnoreFailedSymlinks",
"1", 1 );
246 MIL <<
"~RpmDb()" << endl;
249 MIL <<
"~RpmDb() end" << endl;
259 db_path =
"/var/lib/rpm";
263 PathInfo rpmdb_info(
root() + db_path +
"/Packages");
265 if ( rpmdb_info.isExist() )
266 return rpmdb_info.mtime();
286 #define ENUM_OUT(B,C) str << ( _dbStateInfo & B ? C : '-' )
312 bool quickinit( root_r.empty() );
314 if ( root_r.empty() )
317 if ( dbPath_r.empty() )
318 dbPath_r =
"/var/lib/rpm";
320 if ( ! (root_r.absolute() && dbPath_r.absolute()) )
322 ERR <<
"Illegal root or dbPath: " <<
stringPath( root_r, dbPath_r ) << endl;
326 MIL <<
"Calling initDatabase: " <<
stringPath( root_r, dbPath_r )
327 << ( doRebuild_r ?
" (rebuilddb)" :
"" )
328 << ( quickinit ?
" (quickinit)" :
"" ) << endl;
352 MIL <<
"QUICK initDatabase (no systemRoot set)" << endl;
365 ERR <<
"Cleanup on error: state " << info << endl;
380 MIL <<
"Cleanup: state " << info << endl;
389 MIL <<
"Update mode: Cleanup delayed until closeOldDatabase." << endl;
392 #warning CHECK: notify root about conversion backup.
407 MIL <<
"Syncronizing keys with zypp keyring" << endl;
416 MIL <<
"InitDatabase: " << *
this << endl;
442 ERR <<
"Bad database directory: " << dbInfo.
dbDir() << endl;
449 MIL <<
"Found rpm4 database in " << dbInfo.
dbDir() << endl;
453 MIL <<
"Creating new rpm4 database in " << dbInfo.
dbDir() << endl;
465 DBG <<
"Initial state: " << info_r <<
": " <<
stringPath( root_r, dbPath_r );
484 DBG <<
"Access state: " << info_r <<
": " <<
stringPath( root_r, dbPath_r );
493 bool dbEmpty = dbptr->empty();
496 MIL <<
"Empty rpm4 database " << dbInfo.
dbV4() << endl;
501 MIL <<
"Found rpm3 database " << dbInfo.
dbV3() << endl;
512 WAR <<
"Backup converted rpm3 database failed: error(" << res <<
")" << endl;
519 MIL <<
"Backup converted rpm3 database: " << dbInfo.
dbV3ToV4() << endl;
528 WAR <<
"Non empty rpm3 and rpm4 database found: using rpm4" << endl;
534 DBG <<
"Convert state: " << info_r <<
": " <<
stringPath( root_r, dbPath_r );
540 MIL <<
"Rpm3 database backup: " << dbInfo.
dbV3ToV4() << endl;
552 const char * v3backup =
"packages.rpm3";
553 const char * master =
"Packages";
554 const char * index[] =
576 PathInfo pi( dbdir_r );
579 ERR <<
"Can't remove rpm4 database in non directory: " << dbdir_r << endl;
583 for (
const char ** f = index; *f; ++f )
592 pi( dbdir_r + master );
595 MIL <<
"Removing rpm4 database " << pi << endl;
601 pi( dbdir_r + v3backup );
604 MIL <<
"Removing converted rpm3 database backup " << pi << endl;
618 const char * master =
"packages.rpm";
619 const char * index[] =
621 "conflictsindex.rpm",
632 PathInfo pi( dbdir_r );
635 ERR <<
"Can't remove rpm3 database in non directory: " << dbdir_r << endl;
639 for (
const char ** f = index; *f; ++f )
648 #warning CHECK: compare vs existing v3 backup. notify root
649 pi( dbdir_r + master );
652 Pathname m( pi.path() );
657 Pathname b( m.extend(
"3" ) );
662 Pathname b( m.extend(
".deleted" ) );
672 MIL <<
"(Re)moved rpm3 database to " << pi << endl;
712 MIL <<
"Calling closeDatabase: " << *
this << endl;
743 MIL <<
"closeDatabase: " << *
this << endl;
774 MIL <<
"RpmDb::rebuildDatabase" << *
this << endl;
777 PathInfo dbMaster(
root() +
dbPath() +
"Packages" );
778 PathInfo dbMasterBackup( dbMaster.path().extend(
".y2backup" ) );
782 opts.push_back(
"--rebuilddb");
783 opts.push_back(
"-vv");
790 PathInfo newMaster(
root()
803 if ( ! report->progress( (100 * newMaster.size()) / dbMaster.size(),
root() +
dbPath()) )
805 WAR <<
"User requested abort." << endl;
811 if ( line.compare( 0, 2,
"D:" ) )
813 errmsg += line +
'\n';
821 if ( rpm_status != 0 )
840 void computeKeyRingSync( std::set<Edition> & rpmKeys_r, std::list<PublicKeyData> & zyppKeys_r )
851 void updateIf(
const Edition & rpmKey_r )
853 std::string keyRelease( rpmKey_r.
release() );
854 int comp = _release.compare( keyRelease );
858 _release.swap( keyRelease );
859 _inRpmKeys = &rpmKey_r;
860 _inZyppKeys =
nullptr;
861 if ( !keyRelease.empty() )
862 DBG <<
"Old key in R: gpg-pubkey-" << rpmKey_r.
version() <<
"-" << keyRelease << endl;
864 else if ( comp == 0 )
868 _inRpmKeys = &rpmKey_r;
872 DBG <<
"Old key in R: gpg-pubkey-" << rpmKey_r.
version() <<
"-" << keyRelease << endl;
875 void updateIf(
const PublicKeyData & zyppKey_r )
877 std::string keyRelease( zyppKey_r.gpgPubkeyRelease() );
878 int comp = _release.compare( keyRelease );
882 _release.swap( keyRelease );
883 _inRpmKeys =
nullptr;
884 _inZyppKeys = &zyppKey_r;
885 if ( !keyRelease.empty() )
886 DBG <<
"Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() <<
"-" << keyRelease << endl;
888 else if ( comp == 0 )
892 _inZyppKeys = &zyppKey_r;
896 DBG <<
"Old key in Z: gpg-pubkey-" << zyppKey_r.gpgPubkeyVersion() <<
"-" << keyRelease << endl;
899 std::string _release;
900 const Edition * _inRpmKeys;
901 const PublicKeyData * _inZyppKeys;
906 std::map<std::string,Key> _keymap;
908 for_( it, rpmKeys_r.begin(), rpmKeys_r.end() )
910 _keymap[(*it).version()].updateIf( *it );
913 for_( it, zyppKeys_r.begin(), zyppKeys_r.end() )
915 _keymap[(*it).gpgPubkeyVersion()].updateIf( *it );
919 std::set<Edition> rpmKeys;
920 std::list<PublicKeyData> zyppKeys;
921 for_( it, _keymap.begin(), _keymap.end() )
923 DBG <<
"gpg-pubkey-" << (*it).first <<
"-" << (*it).second._release <<
" "
924 << ( (*it).second._inRpmKeys ?
"R" :
"_" )
925 << ( (*it).second._inZyppKeys ?
"Z" :
"_" ) << endl;
926 if ( ! (*it).second._inRpmKeys )
928 zyppKeys.push_back( *(*it).second._inZyppKeys );
930 if ( ! (*it).second._inZyppKeys )
932 rpmKeys.insert( *(*it).second._inRpmKeys );
935 rpmKeys_r.swap( rpmKeys );
936 zyppKeys_r.swap( zyppKeys );
943 MIL <<
"Going to sync trusted keys..." << endl;
945 std::list<PublicKeyData> zyppKeys( getZYpp()->keyRing()->trustedPublicKeyData() );
946 computeKeyRingSync( rpmKeys, zyppKeys );
947 MIL << (mode_r &
SYNC_TO_KEYRING ?
"" :
"(skip) ") <<
"Rpm keys to export into zypp trusted keyring: " << rpmKeys.size() << endl;
948 MIL << (mode_r &
SYNC_FROM_KEYRING ?
"" :
"(skip) ") <<
"Zypp trusted keys to import into rpm database: " << zyppKeys.size() << endl;
954 MIL <<
"Exporting rpm keyring into zypp trusted keyring" <<endl;
959 TmpFile tmpfile( getZYpp()->tmpPath() );
961 ofstream tmpos( tmpfile.
path().
c_str() );
962 for_( it, rpmKeys.begin(), rpmKeys.end() )
966 getData(
string(
"gpg-pubkey"), *it, result );
967 tmpos << result->tag_description() << endl;
972 getZYpp()->keyRing()->multiKeyImport( tmpfile.
path(),
true );
976 ERR <<
"Could not import keys into in zypp keyring" << endl;
984 MIL <<
"Importing zypp trusted keyring" << std::endl;
985 for_( it, zyppKeys.begin(), zyppKeys.end() )
989 importPubkey( getZYpp()->keyRing()->exportTrustedPublicKey( *it ) );
997 MIL <<
"Trusted keys synced." << endl;
1019 WAR <<
"Key " << pubkey_r <<
" can not be imported. (READONLY MODE)" << endl;
1026 bool hasOldkeys =
false;
1028 for_( it, rpmKeys.begin(), rpmKeys.end() )
1032 MIL <<
"Key " << pubkey_r <<
" is already in the rpm trusted keyring. (skip import)" << endl;
1036 if ( keyEd.version() != (*it).version() )
1039 if ( keyEd.release() < (*it).release() )
1041 MIL <<
"Key " << pubkey_r <<
" is older than one in the rpm trusted keyring. (skip import)" << endl;
1049 MIL <<
"Key " << pubkey_r <<
" will be imported into the rpm trusted keyring." << (hasOldkeys?
"(update)":
"(new)") << endl;
1055 std::string keyName(
"gpg-pubkey-" + keyEd.version() );
1057 opts.push_back (
"-e" );
1058 opts.push_back (
"--allmatches" );
1059 opts.push_back (
"--" );
1060 opts.push_back ( keyName.c_str() );
1073 ERR <<
"Failed to remove key " << pubkey_r <<
" from RPM trusted keyring (ignored)" << endl;
1077 MIL <<
"Key " << pubkey_r <<
" has been removed from RPM trusted keyring" << endl;
1083 opts.push_back (
"--import" );
1084 opts.push_back (
"--" );
1085 opts.push_back ( pubkey_r.
path().asString().c_str() );
1101 _(
"Failed to import public key from file %s: %s"))
1106 MIL <<
"Key " << pubkey_r <<
" imported in rpm trusted keyring." << endl;
1123 set<Edition>::const_iterator found_edition = rpm_keys.end();
1126 for_( it, rpm_keys.begin(), rpm_keys.end() )
1128 if ( (*it).version() == pubkeyVersion )
1136 if (found_edition == rpm_keys.end())
1138 WAR <<
"Key " << pubkey_r.
id() <<
" is not in rpm db" << endl;
1142 string rpm_name(
"gpg-pubkey-" + found_edition->asString());
1145 opts.push_back (
"-e" );
1146 opts.push_back (
"--" );
1147 opts.push_back ( rpm_name.c_str() );
1156 if ( line.substr( 0, 6 ) ==
"error:" )
1158 WAR << line << endl;
1162 DBG << line << endl;
1168 if ( rpm_status != 0 )
1172 _(
"Failed to remove public key %s: %s")) % pubkey_r.
asString()
1177 MIL <<
"Key " << pubkey_r <<
" has been removed from RPM trusted keyring" << endl;
1189 list<PublicKey> ret;
1192 for ( it.
findByName(
string(
"gpg-pubkey" ) ); *it; ++it )
1194 Edition edition = it->tag_edition();
1199 getData(
string(
"gpg-pubkey"), edition, result );
1200 TmpFile file(getZYpp()->tmpPath());
1206 os << result->tag_description();
1217 ERR <<
"Could not dump key " << edition.
asString() <<
" in tmp file " << file.
path() << endl;
1230 for ( it.
findByName(
string(
"gpg-pubkey" ) ); *it; ++it )
1232 Edition edition = it->tag_edition();
1234 ret.insert( edition );
1251 list<FileInfo> result;
1286 if (!name_r.empty())
1288 res = (it->tag_name() == name_r);
1309 return it->tag_name();
1380 librpmDb::db_const_iterator it;
1395 librpmDb::db_const_iterator it;
1396 it.findPackage( name_r );
1410 void RpmDb::getData(
const string & name_r,
const Edition & ed_r,
1413 librpmDb::db_const_iterator it;
1414 it.findPackage( name_r, ed_r );
1427 PathInfo file( path_r );
1428 if ( ! file.isFile() )
1430 ERR <<
"Not a file: " << file << endl;
1434 FD_t fd = ::Fopen( file.asString().c_str(),
"r.ufdio" );
1435 if ( fd == 0 || ::Ferror(fd) )
1437 ERR <<
"Can't open file for reading: " << file <<
" (" << ::Fstrerror(fd) <<
")" << endl;
1443 rpmts ts = ::rpmtsCreate();
1445 ::rpmtsSetVSFlags( ts, RPMVSF_DEFAULT );
1446 int res = ::rpmReadPackageFile( ts, fd, path_r.asString().c_str(), NULL );
1456 case RPMRC_NOTFOUND:
1457 WAR <<
"Signature is unknown type. " << file << endl;
1461 WAR <<
"Signature does not verify. " << file << endl;
1464 case RPMRC_NOTTRUSTED:
1465 WAR <<
"Signature is OK, but key is not trusted. " << file << endl;
1469 WAR <<
"Public key is unavailable. " << file << endl;
1473 ERR <<
"Error reading header." << file << endl;
1489 opts.push_back (
"-V");
1490 opts.push_back (
"--nodeps");
1491 opts.push_back (
"--noscripts");
1492 opts.push_back (
"--nomd5");
1493 opts.push_back (
"--");
1494 opts.push_back (packageName.c_str());
1515 if (line.length() > 12 &&
1516 (line[0] ==
'S' || line[0] ==
's' ||
1517 (line[0] ==
'.' && line[7] ==
'T')))
1522 filename.assign(line, 11, line.length() - 11);
1523 fileList.insert(filename);
1563 args.push_back(
"rpm");
1564 args.push_back(
"--root");
1565 args.push_back(
_root.asString().c_str());
1566 args.push_back(
"--dbpath");
1567 args.push_back(
_dbPath.asString().c_str());
1569 const char* argv[args.size() + opts.size() + 1];
1571 const char** p = argv;
1572 p =
copy (args.begin (), args.end (), p);
1573 p =
copy (opts.begin (), opts.end (), p);
1599 int inputfileFd = ::fileno( inputfile );
1605 FD_SET( inputfileFd, &rfds );
1612 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
1616 ERR <<
"select error: " <<
strerror(errno) << endl;
1617 if ( errno != EINTR )
1623 static size_t linebuffer_size = 0;
1624 static char * linebuffer = 0;
1625 ssize_t nread =
getline( &linebuffer, &linebuffer_size, inputfile );
1628 if ( ::feof( inputfile ) )
1635 if ( linebuffer[nread-1] ==
'\n' )
1637 line += string( linebuffer, nread );
1640 if ( ! ::ferror( inputfile ) || ::feof( inputfile ) )
1643 clearerr( inputfile );
1692 void RpmDb::processConfigFiles(
const string& line,
const string& name,
const char* typemsg,
const char* difffailmsg,
const char* diffgenmsg)
1694 string msg = line.substr(9);
1697 string file1s, file2s;
1701 pos1 = msg.find (typemsg);
1704 if ( pos1 == string::npos )
1707 pos2 = pos1 + strlen (typemsg);
1709 if (pos2 >= msg.length() )
1712 file1 = msg.substr (0, pos1);
1713 file2 = msg.substr (pos2);
1715 file1s = file1.asString();
1716 file2s = file2.asString();
1720 file1 =
_root + file1;
1721 file2 =
_root + file2;
1725 int ret =
diffFiles (file1.asString(), file2.asString(), out, 25);
1731 ERR <<
"Could not create " << file.asString() << endl;
1735 ofstream notify(file.asString().c_str(), ios::out|ios::app);
1738 ERR <<
"Could not open " << file << endl;
1744 notify <<
str::form(
_(
"Changed configuration files for %s:"), name.c_str()) << endl;
1747 ERR <<
"diff failed" << endl;
1749 file1s.c_str(), file2s.c_str()) << endl;
1754 file1s.c_str(), file2s.c_str()) << endl;
1759 if (out.substr(0,4) ==
"--- ")
1761 out.replace(4, file1.asString().length(), file1s);
1764 if (pos != string::npos)
1766 out.replace(pos+5, file2.asString().length(), file2s);
1769 notify << out << endl;
1772 notify.open(
"/var/lib/update-messages/yast2-packagemanager.rpmdb.configfiles");
1777 WAR <<
"rpm created " << file2 <<
" but it is not different from " << file2 << endl;
1793 report->start(filename);
1808 report->finish( excpt_r );
1824 MIL <<
"RpmDb::installPackage(" << filename <<
"," << flags <<
")" << endl;
1833 ERR <<
"backup of " << filename.asString() <<
" failed" << endl;
1836 report->progress( 0 );
1842 opts.push_back(
"-i");
1844 opts.push_back(
"-U");
1846 opts.push_back(
"--percent");
1850 opts.push_back(
"--ignorearch");
1853 opts.push_back(
"--nodigest");
1855 opts.push_back(
"--nosignature");
1857 opts.push_back (
"--excludedocs");
1859 opts.push_back (
"--noscripts");
1861 opts.push_back (
"--force");
1863 opts.push_back (
"--nodeps");
1865 opts.push_back (
"--ignoresize");
1867 opts.push_back (
"--justdb");
1869 opts.push_back (
"--test");
1871 opts.push_back(
"--");
1874 string quotedFilename( rpmQuoteFilename( filename ) );
1875 opts.push_back ( quotedFilename.c_str() );
1882 vector<string> configwarnings;
1884 unsigned linecnt = 0;
1892 if (line.substr(0,2)==
"%%")
1895 sscanf (line.c_str () + 2,
"%d", &percent);
1896 report->progress( percent );
1899 rpmmsg += line+
'\n';
1901 if ( line.substr(0,8) ==
"warning:" )
1903 configwarnings.push_back(line);
1907 rpmmsg +=
"[truncated]\n";
1912 for (vector<string>::iterator it = configwarnings.begin();
1913 it != configwarnings.end(); ++it)
1917 _(
"rpm saved %s as %s, but it was impossible to determine the difference"),
1919 _(
"rpm saved %s as %s.\nHere are the first 25 lines of difference:\n"));
1922 _(
"rpm created %s as %s, but it was impossible to determine the difference"),
1924 _(
"rpm created %s as %s.\nHere are the first 25 lines of difference:\n"));
1927 if ( rpm_status != 0 )
1930 str::form(
"%s install failed", Pathname::basename(filename).c_str()),
1933 sstr <<
"rpm output:" << endl << rpmmsg << endl;
1934 historylog.
comment(sstr.str());
1939 else if ( ! rpmmsg.empty() )
1942 str::form(
"%s installed ok", Pathname::basename(filename).c_str()),
1945 sstr <<
"Additional rpm output:" << endl << rpmmsg << endl;
1946 historylog.
comment(sstr.str());
1950 report->finishInfo(
str::form(
"%s:\n%s\n",
_(
"Additional rpm output"), rpmmsg.c_str() ));
1964 +
"-" + package->edition().version()
1965 +
"-" + package->edition().release()
1966 +
"." + package->arch().asString(), flags );
1979 report->start( name_r );
1988 catch (RpmException & excpt_r)
1994 report->finish( excpt_r );
2011 MIL <<
"RpmDb::doRemovePackage(" << name_r <<
"," << flags <<
")" << endl;
2020 ERR <<
"backup of " << name_r <<
" failed" << endl;
2022 report->progress( 0 );
2026 report->progress( 100 );
2031 opts.push_back(
"-e");
2032 opts.push_back(
"--allmatches");
2035 opts.push_back(
"--noscripts");
2037 opts.push_back(
"--nodeps");
2039 opts.push_back(
"--justdb");
2041 opts.push_back (
"--test");
2044 WAR <<
"IGNORE OPTION: 'rpm -e' does not support '--force'" << endl;
2047 opts.push_back(
"--");
2048 opts.push_back(name_r.c_str());
2060 report->progress( 5 );
2061 unsigned linecnt = 0;
2068 rpmmsg += line+
'\n';
2071 rpmmsg +=
"[truncated]\n";
2072 report->progress( 50 );
2075 if ( rpm_status != 0 )
2078 str::form(
"%s remove failed", name_r.c_str()),
true );
2080 sstr <<
"rpm output:" << endl << rpmmsg << endl;
2081 historylog.
comment(sstr.str());
2086 else if ( ! rpmmsg.empty() )
2089 str::form(
"%s removed ok", name_r.c_str()),
true );
2092 sstr <<
"Additional rpm output:" << endl << rpmmsg << endl;
2093 historylog.
comment(sstr.str());
2097 report->finishInfo(
str::form(
"%s:\n%s\n",
_(
"Additional rpm output"), rpmmsg.c_str() ));
2126 Pathname backupFilename;
2131 INT <<
"_backuppath empty" << endl;
2139 ERR <<
"Error while getting changed files for package " <<
2140 packageName << endl;
2144 if (fileList.size() <= 0)
2146 DBG <<
"package " << packageName <<
" not changed -> no backup" << endl;
2158 struct tm *currentLocalTime = localtime(¤tTime);
2160 int date = (currentLocalTime->tm_year + 1900) * 10000
2161 + (currentLocalTime->tm_mon + 1) * 100
2162 + currentLocalTime->tm_mday;
2168 +
str::form(
"%s-%d-%d.tar.gz",packageName.c_str(), date, num);
2174 if (pi.isExist() && !pi.isFile())
2176 ERR << filestobackupfile.asString() <<
" already exists and is no file" << endl;
2180 ofstream fp ( filestobackupfile.asString().c_str(), ios::out|ios::trunc );
2184 ERR <<
"could not open " << filestobackupfile.asString() << endl;
2188 for (FileList::const_iterator cit = fileList.begin();
2189 cit != fileList.end(); ++cit)
2192 if ( name[0] ==
'/' )
2195 name = name.substr( 1 );
2197 DBG <<
"saving file "<< name << endl;
2202 const char*
const argv[] =
2207 _root.asString().c_str(),
2208 "--ignore-failed-read",
2210 backupFilename.asString().c_str(),
2212 filestobackupfile.asString().c_str(),
2223 for (
string output = tar.receiveLine(); output.length() ;output = tar.receiveLine())
2228 int ret = tar.close();
2232 ERR <<
"tar failed: " << tarmsg << endl;
2237 MIL <<
"tar backup ok" << endl;
2239 str::form(
_(
"created backup %s"), backupFilename.asString().c_str())