From 80c6f977d753eefd57addd9db37e6fe110d05bf5 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Mon, 13 Mar 2017 03:37:52 +0300 Subject: [PATCH] start on queuedctl realization --- sources/CMakeLists.txt | 1 + .../queued-daemon/src/QueuedApplication.cpp | 2 +- .../src/QueuedApplicationInterface.cpp | 6 - .../src/QueuedApplicationInterface.h | 1 - sources/queued-daemon/src/main.cpp | 4 +- sources/queued/include/queued/QueuedCore.h | 7 ++ .../queued/include/queued/QueuedCoreAdaptor.h | 7 ++ .../include/queued/QueuedCoreInterface.h | 7 ++ .../queued/include/queued/QueuedSettings.h | 6 + sources/queued/src/QueuedCore.cpp | 12 ++ sources/queued/src/QueuedCoreAdaptor.cpp | 16 ++- sources/queued/src/QueuedCoreInterface.cpp | 9 ++ sources/queued/src/QueuedSettings.cpp | 36 ++++-- sources/queuedctl/CMakeLists.txt | 13 +++ sources/queuedctl/bash-completions | 0 sources/queuedctl/queued-daemon.1 | 0 sources/queuedctl/src/CMakeLists.txt | 18 +++ sources/queuedctl/src/QueuedctlAuth.cpp | 104 +++++++++++++++++ sources/queuedctl/src/QueuedctlAuth.h | 34 ++++++ sources/queuedctl/src/QueuedctlOption.cpp | 39 +++++++ sources/queuedctl/src/QueuedctlOption.h | 31 +++++ sources/queuedctl/src/main.cpp | 107 ++++++++++++++++++ sources/queuedctl/zsh-completions | 0 23 files changed, 436 insertions(+), 24 deletions(-) create mode 100644 sources/queuedctl/CMakeLists.txt create mode 100644 sources/queuedctl/bash-completions create mode 100644 sources/queuedctl/queued-daemon.1 create mode 100644 sources/queuedctl/src/CMakeLists.txt create mode 100644 sources/queuedctl/src/QueuedctlAuth.cpp create mode 100644 sources/queuedctl/src/QueuedctlAuth.h create mode 100644 sources/queuedctl/src/QueuedctlOption.cpp create mode 100644 sources/queuedctl/src/QueuedctlOption.h create mode 100644 sources/queuedctl/src/main.cpp create mode 100644 sources/queuedctl/zsh-completions diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt index 7fd6adc..1118ec1 100644 --- a/sources/CMakeLists.txt +++ b/sources/CMakeLists.txt @@ -75,6 +75,7 @@ get_directory_property(CMAKE_DEFINITIONS COMPILE_DEFINITIONS) configure_file("${CMAKE_SOURCE_DIR}/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h") add_subdirectory("queued") add_subdirectory("queued-daemon") +add_subdirectory("queuedctl") if (BUILD_TESTING) enable_testing() add_subdirectory("test") diff --git a/sources/queued-daemon/src/QueuedApplication.cpp b/sources/queued-daemon/src/QueuedApplication.cpp index c3076af..ef5d024 100644 --- a/sources/queued-daemon/src/QueuedApplication.cpp +++ b/sources/queued-daemon/src/QueuedApplication.cpp @@ -43,7 +43,7 @@ QueuedApplication::~QueuedApplication() qCDebug(LOG_APP) << __PRETTY_FUNCTION__; QDBusConnection::sessionBus().unregisterObject( - QueuedConfig::DBUS_APPLICATION_PATH); + QueuedConfig::DBUS_APPLICATION_PATH); deinit(); } diff --git a/sources/queued-daemon/src/QueuedApplicationInterface.cpp b/sources/queued-daemon/src/QueuedApplicationInterface.cpp index 50d0776..1af44df 100644 --- a/sources/queued-daemon/src/QueuedApplicationInterface.cpp +++ b/sources/queued-daemon/src/QueuedApplicationInterface.cpp @@ -48,12 +48,6 @@ bool QueuedApplicationInterface::Active() const } -void QueuedApplicationInterface::Close() const -{ - return QCoreApplication::exit(0); -} - - QStringList QueuedApplicationInterface::UIDs() const { QStringList uids; diff --git a/sources/queued-daemon/src/QueuedApplicationInterface.h b/sources/queued-daemon/src/QueuedApplicationInterface.h index 48765db..577d686 100644 --- a/sources/queued-daemon/src/QueuedApplicationInterface.h +++ b/sources/queued-daemon/src/QueuedApplicationInterface.h @@ -35,7 +35,6 @@ public: public slots: bool Active() const; - Q_NOREPLY void Close() const; QStringList UIDs() const; private: diff --git a/sources/queued-daemon/src/main.cpp b/sources/queued-daemon/src/main.cpp index 16e37af..67cea8f 100644 --- a/sources/queued-daemon/src/main.cpp +++ b/sources/queued-daemon/src/main.cpp @@ -95,7 +95,7 @@ int main(int argc, char *argv[]) auto metadata = QueuedDebug::getBuildData(); for (auto &string : metadata) QDebug(QtMsgType::QtInfoMsg).noquote() << string; - return 1; + return 0; } // check if exists @@ -114,7 +114,7 @@ int main(int argc, char *argv[]) // start application instance = new QueuedApplication(nullptr, arguments); // catch SIGHUP - signal(SIGHUP, [](int sig) ->void { + signal(SIGHUP, [](int sig) -> void { qCInfo(LOG_APP) << "Received SIGHUP signal, reinit components"; instance->init(); }); diff --git a/sources/queued/include/queued/QueuedCore.h b/sources/queued/include/queued/QueuedCore.h index b412af8..4a34f83 100644 --- a/sources/queued/include/queued/QueuedCore.h +++ b/sources/queued/include/queued/QueuedCore.h @@ -109,6 +109,13 @@ public: const QString &_password, const uint _permissions, const QueuedLimits::Limits &_limits, const QString &_token); + /** + * @brief try to authorize by given token + * @param _token + * token ID + * @return true if token is valid + */ + bool authorization(const QString &_token); /** * @brief authorize and create new token for user * @param _name diff --git a/sources/queued/include/queued/QueuedCoreAdaptor.h b/sources/queued/include/queued/QueuedCoreAdaptor.h index 6ddc8e3..320fbd0 100644 --- a/sources/queued/include/queued/QueuedCoreAdaptor.h +++ b/sources/queued/include/queued/QueuedCoreAdaptor.h @@ -38,6 +38,13 @@ namespace QueuedCoreAdaptor { // specific methods for control interface +/** + * @brief send TryAuth + * @param _token + * token ID + * @return true if token is valid + */ +bool auth(const QString &_token); /** * @brief send auth method * @param _name diff --git a/sources/queued/include/queued/QueuedCoreInterface.h b/sources/queued/include/queued/QueuedCoreInterface.h index c554010..8c6d58b 100644 --- a/sources/queued/include/queued/QueuedCoreInterface.h +++ b/sources/queued/include/queued/QueuedCoreInterface.h @@ -178,6 +178,13 @@ public slots: * @return true on successful task stop */ bool TaskStop(const qlonglong id, const QString &token); + /** + * @brief try auth by token + * @param token + * token ID + * @return true if token is valid + */ + bool TryAuth(const QString &token); /** * @brief add new user * @param name diff --git a/sources/queued/include/queued/QueuedSettings.h b/sources/queued/include/queued/QueuedSettings.h index 3519a77..3ba0ca3 100644 --- a/sources/queued/include/queued/QueuedSettings.h +++ b/sources/queued/include/queued/QueuedSettings.h @@ -67,6 +67,12 @@ public: * @return default path to configuration file */ static QString defaultPath(); + /** + * @brief default path to cached token + * @return default path to cached token file + * @return + */ + static QString defaultTokenPath(); /** * @brief path to database * @return path to used database diff --git a/sources/queued/src/QueuedCore.cpp b/sources/queued/src/QueuedCore.cpp index a70cdf8..bcf96af 100644 --- a/sources/queued/src/QueuedCore.cpp +++ b/sources/queued/src/QueuedCore.cpp @@ -140,6 +140,18 @@ long long QueuedCore::addUser(const QString &_name, const QString &_email, } +/** + * @fn authorization + */ +bool QueuedCore::authorization(const QString &_token) +{ + bool status = false; + m_users->checkToken(_token, &status); + + return status; +} + + /** * @fn authorization */ diff --git a/sources/queued/src/QueuedCoreAdaptor.cpp b/sources/queued/src/QueuedCoreAdaptor.cpp index 553538f..4a2d69c 100644 --- a/sources/queued/src/QueuedCoreAdaptor.cpp +++ b/sources/queued/src/QueuedCoreAdaptor.cpp @@ -25,8 +25,20 @@ #include #include -#include -#include + + +/** + * @fn auth + */ +bool QueuedCoreAdaptor::auth(const QString &_token) +{ + QVariantList args = {_token}; + return toNativeType(sendRequest(QueuedConfig::DBUS_SERVICE, + QueuedConfig::DBUS_OBJECT_PATH, + QueuedConfig::DBUS_SERVICE, "/TryAuth", + args)) + .toBool(); +} /** diff --git a/sources/queued/src/QueuedCoreInterface.cpp b/sources/queued/src/QueuedCoreInterface.cpp index aab8755..6591c29 100644 --- a/sources/queued/src/QueuedCoreInterface.cpp +++ b/sources/queued/src/QueuedCoreInterface.cpp @@ -193,6 +193,15 @@ bool QueuedCoreInterface::TaskStop(const qlonglong id, const QString &token) } +/** + * @fn TryAuth + */ +bool QueuedCoreInterface::TryAuth(const QString &token) +{ + return m_core->authorization(token); +} + + /** * @fn UserAdd */ diff --git a/sources/queued/src/QueuedSettings.cpp b/sources/queued/src/QueuedSettings.cpp index cbc3fc3..a991afa 100644 --- a/sources/queued/src/QueuedSettings.cpp +++ b/sources/queued/src/QueuedSettings.cpp @@ -88,6 +88,20 @@ QString QueuedSettings::defaultPath() } +/** + * @fn defaultTokenPath + */ +QString QueuedSettings::defaultTokenPath() +{ + QString fileName = QString("%1/queued") + .arg(QStandardPaths::writableLocation( + QStandardPaths::GenericCacheLocation)); + qCInfo(LOG_LIB) << "Cache file location" << fileName; + + return fileName; +} + + /** * @fn path */ @@ -106,24 +120,22 @@ void QueuedSettings::readConfiguration() qCInfo(LOG_LIB) << "Read configuration from" << settings.fileName(); // administrator related settings - settings.beginGroup(QString("Administrator")); - m_cfgAdmin.name - = settings.value(QString("Username"), QString("root")).toString(); - m_cfgAdmin.password = settings.value(QString("Password")).toString(); + settings.beginGroup("Administrator"); + m_cfgAdmin.name = settings.value("Username", "root").toString(); + m_cfgAdmin.password = settings.value("Password").toString(); settings.endGroup(); // database related settings - settings.beginGroup(QString("Database")); - m_cfgDB.driver - = settings.value(QString("Driver"), QString("QSQLITE")).toString(); - m_cfgDB.hostname = settings.value(QString("Hostname")).toString(); - m_cfgDB.password = settings.value(QString("Password")).toString(); + settings.beginGroup("Database"); + m_cfgDB.driver = settings.value("Driver", "QSQLITE").toString(); + m_cfgDB.hostname = settings.value("Hostname").toString(); + m_cfgDB.password = settings.value("Password").toString(); // get standard path for temporary files QString defaultDB = QString("%1/queued.db") .arg(QStandardPaths::writableLocation( QStandardPaths::TempLocation)); - m_cfgDB.path = settings.value(QString("Path"), defaultDB).toString(); - m_cfgDB.port = settings.value(QString("Port")).toInt(); - m_cfgDB.username = settings.value(QString("Username")).toString(); + m_cfgDB.path = settings.value("Path", defaultDB).toString(); + m_cfgDB.port = settings.value("Port").toInt(); + m_cfgDB.username = settings.value("Username").toString(); settings.endGroup(); } diff --git a/sources/queuedctl/CMakeLists.txt b/sources/queuedctl/CMakeLists.txt new file mode 100644 index 0000000..eafba86 --- /dev/null +++ b/sources/queuedctl/CMakeLists.txt @@ -0,0 +1,13 @@ +# set project name +set (SUBPROJECT "queuedctl") +message (STATUS "Subproject ${SUBPROJECT}") + +add_subdirectory ("src") +# build man +file (GLOB SUBPROJECT_MAN_IN "*.1") +file (RELATIVE_PATH SUBPROJECT_MAN "${CMAKE_SOURCE_DIR}" "${SUBPROJECT_MAN_IN}") +configure_file ("${SUBPROJECT_MAN_IN}" "${CMAKE_CURRENT_BINARY_DIR}/${SUBPROJECT_MAN}") + +install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${SUBPROJECT_MAN}" DESTINATION "${DATA_INSTALL_DIR}/man/man1") +install (FILES "bash-completions" DESTINATION "${DATA_INSTALL_DIR}/bash-completion/completions" RENAME "${SUBPROJECT}") +install (FILES "zsh-completions" DESTINATION "${DATA_INSTALL_DIR}/zsh/site-functions" RENAME "_${SUBPROJECT}") diff --git a/sources/queuedctl/bash-completions b/sources/queuedctl/bash-completions new file mode 100644 index 0000000..e69de29 diff --git a/sources/queuedctl/queued-daemon.1 b/sources/queuedctl/queued-daemon.1 new file mode 100644 index 0000000..e69de29 diff --git a/sources/queuedctl/src/CMakeLists.txt b/sources/queuedctl/src/CMakeLists.txt new file mode 100644 index 0000000..0c22400 --- /dev/null +++ b/sources/queuedctl/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# set files +file (GLOB_RECURSE SUBPROJECT_SOURCES "*.cpp") +file (GLOB_RECURSE SUBPROJECT_HEADERS "*.h") + +# include_path +include_directories ("${PROJECT_LIBRARY_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_BINARY_DIR}" + "${PROJECT_TRDPARTY_DIR}" + "${Qt_INCLUDE}") + +qt5_wrap_cpp (SUBPROJECT_MOC_SOURCES "${SUBPROJECT_HEADERS}") + +add_executable ("${SUBPROJECT}" "${SUBPROJECT_HEADERS}" "${SUBPROJECT_SOURCES}" + "${SUBPROJECT_MOC_SOURCES}") +target_link_libraries ("${SUBPROJECT}" "${PROJECT_LIBRARY}" "${Qt_LIBRARIES}") +# install properties +install (TARGETS "${SUBPROJECT}" DESTINATION "${BIN_INSTALL_DIR}") diff --git a/sources/queuedctl/src/QueuedctlAuth.cpp b/sources/queuedctl/src/QueuedctlAuth.cpp new file mode 100644 index 0000000..d7510c5 --- /dev/null +++ b/sources/queuedctl/src/QueuedctlAuth.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 Evgeniy Alekseev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + + +#include "QueuedctlAuth.h" + +#include + +#include + +extern "C" { +#include +#include +} + + +QString QueuedctlAuth::auth(const QString &_user) +{ + qCDebug(LOG_APP) << "Auth as user" << _user; + + // read password + // do not show input characters + struct termios tty; + ::tcgetattr(STDIN_FILENO, &tty); + tty.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &tty); + + qInfo() << "Password for" << _user; + QTextStream stream(stdin); + QString password; + stream >> password; + + return QueuedCoreAdaptor::auth(_user, password); +} + + +QString QueuedctlAuth::getToken(const QString &_cache, const QString &_user) +{ + qCDebug(LOG_APP) << "Get token using cache" << _cache << "and user" + << _user; + + QString tokenId = token(_cache); + if (tryAuth(tokenId)) { + return tokenId; + } else { + tokenId = auth(_user); + setToken(tokenId, _cache); + return getToken(_cache, _user); + } +} + + +void QueuedctlAuth::parser(QCommandLineParser &_parser) +{ + _parser.clearPositionalArguments(); +} + + +void QueuedctlAuth::setToken(const QString &_token, const QString &_cache) +{ + qCDebug(LOG_APP) << "Save token to" << _cache; + + QSettings settings(_cache, QSettings::IniFormat); + settings.beginGroup("queuedctl"); + settings.setValue("Token", _token); + settings.endGroup(); + + settings.sync(); +} + + +QString QueuedctlAuth::token(const QString &_cache) +{ + qCDebug(LOG_APP) << "Load token from" << _cache; + + QString token; + + QSettings settings(_cache, QSettings::IniFormat); + settings.beginGroup("queuedctl"); + token = settings.value("Token").toString(); + settings.endGroup(); + + return token; +} + + +bool QueuedctlAuth::tryAuth(const QString &_token) +{ + qCDebug(LOG_APP) << "Try auth with" << _token; + + return QueuedCoreAdaptor::auth(_token); +} diff --git a/sources/queuedctl/src/QueuedctlAuth.h b/sources/queuedctl/src/QueuedctlAuth.h new file mode 100644 index 0000000..2ddfb9e --- /dev/null +++ b/sources/queuedctl/src/QueuedctlAuth.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 Evgeniy Alekseev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + + +#ifndef QUEUEDCTLAUTH_H +#define QUEUEDCTLAUTH_H + +#include + + +namespace QueuedctlAuth +{ +QString auth(const QString &_user); +QString getToken(const QString &_cache, const QString &_user); +void parser(QCommandLineParser &_parser); +void setToken(const QString &_token, const QString &_cache); +QString token(const QString &_cache); +bool tryAuth(const QString &_token); +}; + + +#endif /* QUEUEDCTLAUTH_H */ diff --git a/sources/queuedctl/src/QueuedctlOption.cpp b/sources/queuedctl/src/QueuedctlOption.cpp new file mode 100644 index 0000000..928bde5 --- /dev/null +++ b/sources/queuedctl/src/QueuedctlOption.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016 Evgeniy Alekseev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + + +#include "QueuedctlOption.h" + +#include + +#include "QueuedctlAuth.h" + + +bool QueuedctlOption::editOption(const QString &_option, const QVariant &_value, + const QString &_cache, const QString &_user) +{ + qCDebug(LOG_APP) << "Edit option" << _option << "to" << _value; + + QString token = QueuedctlAuth::getToken(_cache, _user); + return QueuedCoreAdaptor::sendOptionEdit(_option, _value, token); +} + + +void QueuedctlOption::parser(QCommandLineParser &_parser) +{ + _parser.clearPositionalArguments(); + _parser.addPositionalArgument("option", "Option name."); + _parser.addPositionalArgument("value", "Option value."); +} diff --git a/sources/queuedctl/src/QueuedctlOption.h b/sources/queuedctl/src/QueuedctlOption.h new file mode 100644 index 0000000..fad7171 --- /dev/null +++ b/sources/queuedctl/src/QueuedctlOption.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016 Evgeniy Alekseev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + + +#ifndef QUEUEDCTLOPTION_H +#define QUEUEDCTLOPTION_H + +#include + + +namespace QueuedctlOption +{ +bool editOption(const QString &_option, const QVariant &_value, + const QString &_cache, const QString &_user); +void parser(QCommandLineParser &_parser); +}; + + +#endif /* QUEUEDCTLOPTION_H */ diff --git a/sources/queuedctl/src/main.cpp b/sources/queuedctl/src/main.cpp new file mode 100644 index 0000000..e931c53 --- /dev/null +++ b/sources/queuedctl/src/main.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 Evgeniy Alekseev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + + +#include +#include + +#include + +#include "QueuedctlAuth.h" +#include "QueuedctlOption.h" +#include "version.h" + +extern "C" { +#include +} + + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationName(NAME); + app.setApplicationVersion(VERSION); + + // parser + QCommandLineParser parser; + parser.setApplicationDescription( + "Daemon for starting jobs to queue of calculations"); + parser.addHelpOption(); + parser.addVersionOption(); + // info + QCommandLineOption infoOption(QStringList() << "i" + << "info", + "Show additional info."); + parser.addOption(infoOption); + + // debug mode + QCommandLineOption debugOption(QStringList() << "d" + << "debug", + "Print debug information."); + parser.addOption(debugOption); + + // configuration option + QCommandLineOption tokenOption(QStringList() << "t" + << "token", + "Path to cached token.", "token", + QueuedSettings::defaultTokenPath()); + parser.addOption(tokenOption); + QCommandLineOption userOption(QStringList() << "u" + << "user", + "User to login instead of current one.", + "user", ::getlogin()); + parser.addOption(userOption); + + parser.addPositionalArgument("command", "Command to execute."); + + // pre-parse + parser.parse(QCoreApplication::arguments()); + QStringList args = parser.positionalArguments(); + QString command = args.isEmpty() ? QString() : args.first(); + + if (command == "auth") { + QueuedctlAuth::parser(parser); + } else if (command == "option-edit") { + QueuedctlOption::parser(parser); + } else if (command == "task-add") { + parser.clearPositionalArguments(); + } else if (command == "task-edit") { + parser.clearPositionalArguments(); + } else if (command == "user-add") { + parser.clearPositionalArguments(); + } else if (command == "user-edit") { + parser.clearPositionalArguments(); + } else { + parser.process(app); + qWarning() << "Unknown command" << command; + parser.showHelp(1); + } + + parser.process(app); + + // show info and exit + if (parser.isSet(infoOption)) { + auto metadata = QueuedDebug::getBuildData(); + for (auto &string : metadata) + QDebug(QtMsgType::QtInfoMsg).noquote() << string; + return 0; + } + + // enable debug + if (parser.isSet(debugOption)) + QueuedDebug::enableDebug(); + + return 0; +} diff --git a/sources/queuedctl/zsh-completions b/sources/queuedctl/zsh-completions new file mode 100644 index 0000000..e69de29