diff --git a/src/engine/experimental/databasesanitizer.cpp b/src/engine/experimental/databasesanitizer.cpp index 2a3a8639..34051369 100644 --- a/src/engine/experimental/databasesanitizer.cpp +++ b/src/engine/experimental/databasesanitizer.cpp @@ -1,214 +1,272 @@ /* * This file is part of the KDE Baloo project. * Copyright 2018 Michael Heidelbach * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "databasesanitizer.h" #include "documenturldb.h" -#include "baloodebug.h" +#include "fsutils.h" +#include "idutils.h" + +#include #include #include +#include namespace Baloo { - + class DatabaseSanitizerImpl { public: DatabaseSanitizerImpl(const Database& db, Transaction::TransactionType type) : m_transaction(new Transaction(db, type)) { } public: /** * \brief Basic info about database items */ struct FileInfo { - quint32 deviceId = 0; + quint32 deviceId = 0; quint32 inode = 0; QString url = QString(); bool accessible = true; }; - + void printProgress(QTextStream& out, uint& cur, const uint max, const uint step) const { if (cur % step == 0) { out << QStringLiteral("%1%2\r").arg(100 * cur / max, 6).arg("%", -16); out.flush(); } cur++; } - + /** * Create a list of \a FileInfo items. - * + * * \p deviceIDs filter by device ids. If the vector is empty no filtering is done * and every item is collected. * Positive numbers are including filters collecting only the mentioned device ids. * Negative numbers are excluding filters collecting everything but the mentioned device ids. - * + * * \p missingOnly Only inaccessible items are collected. - * + * * \p urlFilter Filter result urls. Default is null = Collect everything. */ QVector createList( - const QVector& deviceIds, - const bool purging, + const QVector& deviceIds, + const bool purging, const QSharedPointer& urlFilter ) const { Q_ASSERT(m_transaction); - const auto docUrlDb = DocumentUrlDB(m_transaction->m_dbis.idTreeDbi, - m_transaction->m_dbis.idFilenameDbi, + const auto docUrlDb = DocumentUrlDB(m_transaction->m_dbis.idTreeDbi, + m_transaction->m_dbis.idFilenameDbi, m_transaction->m_txn); const auto map = docUrlDb.toTestMap(); const auto keys = map.keys(); QVector result; result.reserve(keys.count()); uint i = 0; uint max = keys.count(); QVector includeIds; QVector excludeIds; for (qint64 deviceId : deviceIds) { if (deviceId > 0) { includeIds.append(deviceId); } else if (deviceId < 0) { excludeIds.append(-deviceId); } } - + QTextStream err(stderr); for (quint64 id: keys) { printProgress(err, i, max, 100); - + const quint32* arr = reinterpret_cast(&id); const auto url = docUrlDb.get(id); FileInfo info; info.deviceId = arr[0]; info.inode = arr[1]; info.url = url; info.accessible = !url.isEmpty() && QFileInfo::exists(url); if ((!includeIds.isEmpty() && !includeIds.contains(info.deviceId)) || (!excludeIds.isEmpty() && excludeIds.contains(info.deviceId)) || (purging && info.accessible) || (urlFilter && !urlFilter->match(info.url).hasMatch()) ) { continue; } result.append(info); } return result; } - - QMultiHash createDeviceList(const QVector& deviceIds) + + struct DeviceInfo { + quint32 id = 0; + int items = 0; + FSUtils::DeviceInfo fsInfo = {}; + bool mounted = false; + }; + + int fillInDeviceInfo(const quint32 id, DeviceInfo& deviceInfo) { + static QMap deviceInfos = []() { + QMap result; + const auto devices = FSUtils::attachedDevices(); + for (const auto& dev : devices) { + const QByteArray filePath = QFile::encodeName(dev.mountpoint); + const auto fsinfo = filePathToStat(filePath); + const quint32 id = static_cast(fsinfo.st_dev); + DeviceInfo devInfo; + devInfo.id = id; + devInfo.fsInfo = dev; + devInfo.mounted = true && (dev.filesystem != QLatin1String("tmpfs")); + qDebug() << "filesystem" << dev.filesystem; + result[id] = devInfo; + } + return result; + }(); + + deviceInfo.id = id; + if (deviceInfos.count(id) == 1) { + const DeviceInfo devInf = deviceInfos[deviceInfo.id]; + deviceInfo.fsInfo = devInf.fsInfo ; + deviceInfo.mounted = devInf.mounted; + return 0; + } + return 1; + } + + QMap createDeviceList(const QVector& infos) { - auto infos = createList(deviceIds, false, nullptr); - QMultiHash usedDevices; - for (const auto& info: infos) { - usedDevices.insert(info.deviceId, info); + QMap usedDevices; + for (const auto& info : infos) { + usedDevices[info.deviceId].items++; + } + for (auto it = usedDevices.begin(), end = usedDevices.end(); it != end; it++) { + fillInDeviceInfo(it.key(), it.value()); } return usedDevices; } - + private: Transaction* m_transaction; }; } using namespace Baloo; DatabaseSanitizer::DatabaseSanitizer(const Database& db, Baloo::Transaction::TransactionType type) : m_pimpl(new DatabaseSanitizerImpl(db, type)) { } DatabaseSanitizer::DatabaseSanitizer(Database* db, Transaction::TransactionType type) : DatabaseSanitizer(*db, type) { } DatabaseSanitizer::~DatabaseSanitizer() { delete m_pimpl; m_pimpl = nullptr; } /** * Create a list of \a FileInfo items and print it to stdout. -* +* * \p deviceIDs filter by device ids. If the vector is empty no filtering is done * and everything is printed. * Positive numbers are including filters printing only the mentioned device ids. * Negative numbers are excluding filters printing everything but the mentioned device ids. -* +* * \p missingOnly Simulate purging operation. Only inaccessible items are printed. -* +* * \p urlFilter Filter result urls. Default is null = Print everything. */ void DatabaseSanitizer::printList( - const QVector& deviceIds, - const bool missingOnly, + const QVector& deviceIds, + const bool missingOnly, const QSharedPointer& urlFilter) { auto infos = m_pimpl->createList(deviceIds, missingOnly, urlFilter); const auto sep = QLatin1Char(' '); QTextStream out(stdout); QTextStream err(stderr); for (const auto& info: infos) { if (!missingOnly) { out << QStringLiteral("%1").arg(info.accessible ? "+" : "!") << sep; } else if (!info.accessible) { out << i18n("Missing:") << sep; } else { Q_ASSERT(false); continue; } out << QStringLiteral("device: %1").arg(info.deviceId) << sep << QStringLiteral("inode: %1").arg(info.inode) << sep << QStringLiteral("url: %1").arg(info.url) << endl; } err << i18n("Found %1 matching items", infos.count()) << endl; - + } void DatabaseSanitizer::printDevices(const QVector& deviceIds, const bool missingOnly) { - Q_UNUSED(missingOnly) - /* - * TODO: Implement missingOnly filter. Checking for file existence - * will not work. We need to read /etc/mtab or so. - */ - auto usedDevices = m_pimpl->createDeviceList(deviceIds); - + auto infos = m_pimpl->createList(deviceIds, false, nullptr); + auto usedDevices = m_pimpl->createDeviceList(infos); + const auto sep = QLatin1Char(' '); QTextStream out(stdout); QTextStream err(stderr); - - for (const auto& dev: usedDevices.uniqueKeys()) { - out << "Device:" << dev - << sep << usedDevices.values(dev).count() << sep << "items" - << endl; + int matchCount = 0; + for (const auto& dev : usedDevices) { + if (missingOnly && dev.mounted) { + continue; + } + matchCount++; + // TODO coloring would be nice, but "...|grep '^!'" does not work with it. + // out << QStringLiteral("%1").arg(dev.mounted ? "+" : "\033[1;31m!") + // Can be done, see: https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qlogging.cpp#n263 + out << QStringLiteral("%1").arg(dev.mounted ? "+" : "!") + << sep << QStringLiteral("device:%1").arg(dev.id) + << sep << QStringLiteral("[%1:%2]") + .arg(major(dev.id), 4, 16, QLatin1Char('0')) + .arg(minor(dev.id), 4, 16, QLatin1Char('0')) + << sep << QStringLiteral("indexed-items:%1").arg(dev.items); + + if (dev.mounted) { + out + << sep << QStringLiteral("fstype:%1").arg(dev.fsInfo.filesystem) + << sep << QStringLiteral("fsname:%1").arg(dev.fsInfo.name) + << sep << QStringLiteral("mount:%1").arg(dev.fsInfo.mountpoint) + ; + } + // TODO: see above + // out << QStringLiteral("\033[0m") << endl; + out << endl; } - - err << i18n("Found %1 matching items", usedDevices.count()) << endl; + + err << i18n("Found %1 matching in %2 devices", matchCount, usedDevices.size()) << endl; } diff --git a/src/engine/experimental/databasesanitizer.h b/src/engine/experimental/databasesanitizer.h index 2a1e6cd6..612018cd 100644 --- a/src/engine/experimental/databasesanitizer.h +++ b/src/engine/experimental/databasesanitizer.h @@ -1,71 +1,71 @@ /* * This file is part of the KDE Baloo project. * Copyright 2018 Michael Heidelbach * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BALOODATABASESANITIZER_H #define BALOODATABASESANITIZER_H #include "transaction.h" -namespace Baloo +namespace Baloo { class DatabaseSanitizerImpl; /** * \brief Provide methods to show database problems and * sanitize them. */ class BALOO_ENGINE_EXPORT DatabaseSanitizer { public: DatabaseSanitizer(const Database& db, Transaction::TransactionType type); DatabaseSanitizer(Database* db, Transaction::TransactionType type); ~DatabaseSanitizer(); - - /** + + /** * Print database content to stdout - * + * * \p deviceIDs filter by device ids. Negative numbers list everything but... * with empty \p deviceIDs(default) everything is printed. - * + * * \p missingOnly Simulate purging operation. Only inaccessible items are printed. - * + * * \p urlFilter Filter result urls. Default is null = Print everything. */ - void printList(const QVector& deviceIds, - const bool missingOnly, + void printList(const QVector& deviceIds, + const bool missingOnly, const QSharedPointer& urlFilter ); - /** + /** * Print info about known devices to stdout - * + * * \p deviceIDs filter by device ids. Negative numbers list everything but... * with empty \p deviceIDs(default) everything is printed. - * + * * \p missingOnly Only inaccessible items are printed. */ void printDevices(const QVector& deviceIds, const bool missingOnly = false); - + private: DatabaseSanitizer(const DatabaseSanitizer& rhs) = delete; DatabaseSanitizerImpl* m_pimpl; }; } #endif // BALOODATABASESANITIZER_H diff --git a/src/engine/fsutils.cpp b/src/engine/fsutils.cpp index 76ab12f8..4971bf46 100644 --- a/src/engine/fsutils.cpp +++ b/src/engine/fsutils.cpp @@ -1,109 +1,137 @@ /* * Copyright (C) 2010 Tobias Koenig * Copyright (C) 2014 Daniel Vrátil * Copyright (C) 2015 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "fsutils.h" #include #ifdef Q_OS_LINUX #include #include #include #include #include #endif using namespace Baloo; QString FSUtils::getDirectoryFileSystem(const QString &directory) { #ifndef Q_OS_LINUX return QString(); #else QString bestMatchPath; QString bestMatchFS; FILE *mtab = setmntent("/etc/mtab", "r"); if (!mtab) { return QString(); } while (mntent *mnt = getmntent(mtab)) { if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) { continue; } const QString dir = QString::fromLocal8Bit(mnt->mnt_dir); if (!directory.startsWith(dir) || dir.length() < bestMatchPath.length()) { continue; } bestMatchPath = dir; bestMatchFS = QString::fromLocal8Bit(mnt->mnt_type); } endmntent(mtab); return bestMatchFS; #endif } void FSUtils::disableCoW(const QString &path) { #ifndef Q_OS_LINUX Q_UNUSED(path); #else // from linux/fs.h, so that Baloo does not depend on Linux header files #ifndef FS_IOC_GETFLAGS #define FS_IOC_GETFLAGS _IOR('f', 1, long) #endif #ifndef FS_IOC_SETFLAGS #define FS_IOC_SETFLAGS _IOW('f', 2, long) #endif // Disable COW on file #ifndef FS_NOCOW_FL #define FS_NOCOW_FL 0x00800000 #endif ulong flags = 0; const int fd = open(qPrintable(path), O_RDONLY); if (fd == -1) { qWarning() << "Failed to open" << path << "to modify flags (" << errno << ")"; return; } if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) { qWarning() << "ioctl error: failed to get file flags (" << errno << ")"; close(fd); return; } if (!(flags & FS_NOCOW_FL)) { flags |= FS_NOCOW_FL; if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) { qWarning() << "ioctl error: failed to set file flags (" << errno << ")"; close(fd); return; } } close(fd); #endif } + +const QVector FSUtils::attachedDevices() +{ + QVector result; +#ifndef Q_OS_LINUX + return result; +#else + FILE *mtab = setmntent("/etc/mtab", "r"); + if (!mtab) { + return result; + } + while (mntent *mnt = getmntent(mtab)) { + if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) { + continue; + } + DeviceInfo info; + info.mountpoint = QString::fromLocal8Bit(mnt->mnt_dir); + info.filesystem = QString::fromLocal8Bit(mnt->mnt_type); + info.name = QString::fromLocal8Bit(mnt->mnt_fsname); + info.options = QString::fromLocal8Bit(mnt->mnt_opts).split(QLatin1Char(',')); + result.append(info); + } + + endmntent(mtab); + + return result; +#endif +} diff --git a/src/engine/fsutils.h b/src/engine/fsutils.h index 17a2a5ea..35a1008a 100644 --- a/src/engine/fsutils.h +++ b/src/engine/fsutils.h @@ -1,51 +1,62 @@ /* * This file is part of the KDE Baloo Project * Copyright (C) 2015 Vishesh Handa * Copyright (C) 2010 Tobias Koenig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #ifndef BALOO_ENGINE_FSUTILS_H #define BALOO_ENGINE_FSUTILS_H #include +#include namespace Baloo { namespace FSUtils { /** * Returns name of filesystem that @p directory is stored on. This * only works on Linux and returns empty string on other platforms or when it's * unable to detect the filesystem. */ QString getDirectoryFileSystem(const QString &directory); /** * Disables filesystem copy-on-write feature on given file or directory. * Only works on Linux and does nothing on other platforms. * * It was tested only with Btrfs but in theory can be called on any FS that * supports NOCOW. */ void disableCoW(const QString &path); +struct DeviceInfo { + quint64 id = 0; + QString filesystem; + QString name; + QString mountpoint; + QStringList options; +}; + +const QVector attachedDevices(); + } } #endif diff --git a/src/tools/experimental/baloodb/main.cpp b/src/tools/experimental/baloodb/main.cpp index fa111791..d59db60b 100644 --- a/src/tools/experimental/baloodb/main.cpp +++ b/src/tools/experimental/baloodb/main.cpp @@ -1,265 +1,265 @@ /* * * Copyright 2018 Michael Heidelbach * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "global.h" #include "experimental/databasesanitizer.h" #include #include #include #include #include #include #include #include #include using namespace Baloo; struct Command { const QString name; const QString description; const QStringList args; const QStringList options; }; const auto options = QList{ QCommandLineOption{ QStringList{QStringLiteral("i"), QStringLiteral("device-id")}, i18n("Filter by device id." "\n0 (default) does not filter and everything is printed." "\nPositive numbers are including filters printing only the mentioned device id." "\nNegative numbers are excluding filters printing everything but the mentioned device id." "\nMay be given multiple times."), i18n("integer"), 0 }, QCommandLineOption{ QStringList{QStringLiteral("D"), QStringLiteral("dry-run")}, i18n("Print results of a prune operation, but do not change anything." "\nOnly applies to \"%1\" command", QStringLiteral("prune")) }, QCommandLineOption{ QStringList{QStringLiteral("m"), QStringLiteral("missing-only")}, i18n("List only inaccessible entries.\nOnly applies to \"%1\"", QStringLiteral("list")) } }; const auto commands = std::vector{ Command{ QStringLiteral("list"), i18n("List database contents. Use a regular expression as argument to filter output"), QStringList{ QStringLiteral("pattern") }, QStringList{ QStringLiteral("missing-only"), QStringLiteral("device-id") } }, /*TODO: Command{ QStringLiteral("check"), i18n("Check database contents. " "Beware this may take very long to execute"), QStringList{}, QStringList{} }, */ /*TODO: Command{ QStringLiteral("prune"), i18n("Remove stale database entries"), QStringList{ QStringLiteral("pattern") }, QStringList{ QStringLiteral("dry-run"), QStringLiteral("device-id") } }, */ Command{ QStringLiteral("devices"), i18n("List devices"), QStringList{}, QStringList{QStringLiteral("missing-only")} } }; const QStringList allowedCommands() { QStringList names; for (const auto& c : commands) { names.append(c.name); } return names; } const QStringList getOptions(const QString& name) { for (const auto& c : commands) { if (c.name == name) { return c.options; } } return QStringList(); } QString createDescription() { QStringList allowedcommands; for (const auto& c: commands) { auto options = getOptions(c.name); const QString optionStr = options.isEmpty() ? QString() : QStringLiteral(" [--%1]").arg(options.join(QLatin1Literal("] [--"))); QString argumentStr; if (!c.args.isEmpty() ) { argumentStr = QStringLiteral(" [%1]").arg(c.args.join(QStringLiteral("] ["))); } const QString commandStr = QStringLiteral("%1%2%3") .arg(c.name) .arg(optionStr) .arg(argumentStr); const QString str = QStringLiteral("%1 %2") .arg(commandStr, -48) .arg(c.description); allowedcommands.append(str); } const QString allCommandsStr = allowedcommands.join(QStringLiteral("\n ")); return i18n("\n\nCommands:\n %1", allCommandsStr); } int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); KAboutData aboutData(QStringLiteral("baloodb"), i18n("Baloo Database Sanitizer"), PROJECT_VERSION, i18n("The Baloo Database Lister & Sanitizer"), KAboutLicense::GPL, i18n("(c) 2018, Michael Heidelbach")); aboutData.addAuthor(i18n("Michael Heidelbach"), i18n("Maintainer"), QStringLiteral("ottwolt@gmail.com")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; parser.addOptions(options); parser.addPositionalArgument(QStringLiteral("command"), i18n("The command to execute"), allowedCommands().join(QStringLiteral("|")) ); parser.addPositionalArgument(QStringLiteral("pattern"), i18nc("Command", "A regular expression applied to the URL of database items" "\nExample: %1" , "baloodb list '^/media/videos/series'" ) ); const QString warnExperiment = QStringLiteral( "===\nPlease note: This is an experimental tool. Command line switches or their meaning may change.\n==="); parser.setApplicationDescription(warnExperiment + createDescription()); parser.addVersionOption(); parser.addHelpOption(); parser.process(app); if (parser.positionalArguments().isEmpty()) { qDebug() << "No command"; parser.showHelp(1); } auto args = parser.positionalArguments(); auto command = args.at(0); args.removeFirst(); if(!allowedCommands().contains(command)) { qDebug() << "Unknown command" << command; parser.showHelp(1); } const auto optNames = parser.optionNames(); const auto allowedOptions = getOptions(command); QVector deviceIds; for (const auto& dev : parser.values(QStringLiteral("device-id"))) { deviceIds.append(dev.toInt()); } const bool missingOnly = parser.isSet(QStringLiteral("missing-only")); const QString pattern = args.isEmpty() ? QString() : args.at(0); const QSharedPointer urlFilter(pattern.isEmpty() ? nullptr : new QRegularExpression{pattern}); auto db = globalDatabaseInstance(); QTextStream err(stderr); QElapsedTimer timer; timer.start(); if (command == QStringLiteral("list")) { if (!db->open(Database::ReadOnlyDatabase)) { err << i18n("Baloo Index could not be opened") << endl; return 1; } DatabaseSanitizer san(db, Transaction::ReadOnly); err << i18n("Listing database contents...") << endl; san.printList(deviceIds, missingOnly, urlFilter); } else if (command == QStringLiteral("devices")) { if (!db->open(Database::ReadOnlyDatabase)) { err << i18n("Baloo Index could not be opened") << endl; return 1; } DatabaseSanitizer san(db, Transaction::ReadOnly); err << i18n("Listing database contents...") << endl; - san.printDevices(deviceIds); + san.printDevices(deviceIds, missingOnly); } else if (command == QStringLiteral("clean")) { /* TODO: add prune command */ parser.showHelp(1); } else if (command == QStringLiteral("check")) { parser.showHelp(1); /* TODO: After check methods are improved Database *db = globalDatabaseInstance(); if (!db->open(Database::ReadOnlyDatabase)) { err << i18n("Baloo Index could not be opened") << endl; return 1; } Transaction tr(db, Transaction::ReadOnly); err << i18n("Checking file paths ... ") << endl; tr.checkFsTree(); err << i18n("Checking postings ... ") << endl; tr.checkTermsDbinPostingDb(); err << i18n("Checking terms ... ") << endl; tr.checkPostingDbinTermsDb(); */ } err << i18n("Elapsed: %1 secs", timer.nsecsElapsed() / 1000000000.0) << endl; return 0; }