~aleteoryx/muditaos

841ba62b1682d7399f477b6439e7fc4607fd0339 — Marek Niepieklo 4 years ago abc1c1f
[CP-1103] Harmony device log is not sent to support

Isolated common Device Info EP
Added returning of diagnostic data info to Harmony DeviceInfo EP
Updated get_os_log.py tool
M module-services/service-desktop/endpoints/CMakeLists.txt => module-services/service-desktop/endpoints/CMakeLists.txt +2 -0
@@ 50,6 50,7 @@ target_sources(
        developerMode/Mode/UI_Helper.cpp
        developerMode/event/ATRequest.cpp
        developerMode/event/DomRequest.cpp
        deviceInfo/DeviceInfoEndpointCommon.cpp
        factoryReset/FactoryResetEndpoint.cpp
        filesystem/FileContext.cpp
        filesystem/FileOperations.cpp


@@ 74,6 75,7 @@ target_sources(
        include/endpoints/developerMode/Mode/UI_Helper.hpp
        include/endpoints/developerMode/event/ATRequest.hpp
        include/endpoints/developerMode/event/DomRequest.hpp
        include/endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp
        include/endpoints/factoryReset/FactoryResetEndpoint.hpp
        include/endpoints/filesystem/FileContext.hpp
        include/endpoints/filesystem/FileOperations.hpp

A module-services/service-desktop/endpoints/deviceInfo/DeviceInfoEndpointCommon.cpp => module-services/service-desktop/endpoints/deviceInfo/DeviceInfoEndpointCommon.cpp +182 -0
@@ 0,0 1,182 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp>
#include <endpoints/message/Sender.hpp>
#include <endpoints/JsonKeyNames.hpp>

#include <EventStore.hpp>
#include <product/version.hpp>
#include <service-desktop/ServiceDesktop.hpp>

#include <cstdint>
#include <string>
#include <vector>
#include <sys/statvfs.h>
#include <purefs/filesystem_paths.hpp>

#include <ctime>

namespace sdesktop::endpoints
{
    auto DeviceInfoEndpointCommon::handle(Context &context) -> void
    {
        http::Code status;
        switch (context.getMethod()) {
        case http::Method::get:
            status = handleGet(context);
            break;
        default:
            status = http::Code::BadRequest;
            break;
        }
        context.setResponseStatus(status);
        sender::putToSendQueue(context.createSimpleResponse());
    }

    auto DeviceInfoEndpointCommon::handleGet(Context &context) -> http::Code
    {
        const auto &requestBody = context.getBody();

        if (requestBody[json::fileList].is_number()) {

            const auto diagFileType = parseDiagnosticFileType(requestBody[json::fileList]);

            if (!magic_enum::enum_contains<DiagnosticFileType>(diagFileType)) {
                LOG_ERROR("Bad diagnostic type %d requested", static_cast<unsigned>(diagFileType));

                return http::Code::BadRequest;
            }

            return gatherListOfDiagnostics(context, diagFileType);
        }
        else {
            return getDeviceInfo(context);
        }
    }

    auto DeviceInfoEndpointCommon::parseDiagnosticFileType(const json11::Json &fileList) -> DiagnosticFileType
    {
        return magic_enum::enum_cast<DiagnosticFileType>(fileList.int_value()).value();
    }

    auto DeviceInfoEndpointCommon::gatherListOfDiagnostics(Context &context, DiagnosticFileType diagDataType)
        -> http::Code
    {
        std::vector<std::string> fileList;
        auto status = http::Code::NoContent;

        try {
            requestLogsFlush();
        }
        catch (const std::runtime_error &e) {
            LOG_ERROR("Logs flush exception: %s", e.what());
        }

        switch (diagDataType) {
        case DiagnosticFileType::Logs: {
            fileList = listDirectory(purefs::dir::getLogsPath());
            break;
        }

        case DiagnosticFileType::CrashDumps: {
            fileList = listDirectory(purefs::dir::getCrashDumpsPath());
            break;
        }
        }

        if (!fileList.empty()) {
            status = http::Code::OK;
            context.setResponseBody(fileListToJsonObject(fileList));
        }

        return status;
    }

    auto DeviceInfoEndpointCommon::requestLogsFlush() const -> void
    {
        auto owner = dynamic_cast<ServiceDesktop *>(ownerServicePtr);
        if (owner) {
            owner->requestLogsFlush();
        }
    }

    auto DeviceInfoEndpointCommon::fileListToJsonObject(const std::vector<std::string> &fileList) const
        -> json11::Json::object const
    {
        json11::Json::array fileArray;

        for (const auto &file : fileList) {
            fileArray.push_back(file);
        }

        return json11::Json::object{{json::files, fileArray}};
    }

    auto DeviceInfoEndpointCommon::listDirectory(const std::string &path) -> std::vector<std::string>
    {
        std::vector<std::string> entries;

        for (const auto &entry : std::filesystem::directory_iterator(path)) {
            entries.push_back(entry.path());
        }

        return entries;
    }

    auto DeviceInfoEndpointCommon::getStorageStats(const std::string &path) -> std::tuple<long, long>
    {
        std::unique_ptr<struct statvfs> vfstat = std::make_unique<struct statvfs>();
        if (statvfs(path.c_str(), vfstat.get()) < 0) {
            return {-1, -1};
        }

        unsigned long totalMbytes = (uint64_t(vfstat->f_blocks) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);
        unsigned long freeMbytes  = (uint64_t(vfstat->f_bfree) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);

        return {totalMbytes, freeMbytes};
    }

    auto DeviceInfoEndpointCommon::getStorageInfo() -> std::tuple<long, long, long>
    {
        const std::array<std::filesystem::path, 2> storagePaths{purefs::dir::getRootDiskPath(),
                                                                purefs::dir::getPreviousOSPath()};

        unsigned long totalMbytes    = 0;
        unsigned long freeUserMbytes = 0;
        unsigned long freePercent    = 0;

        for (const auto &p : storagePaths) {
            auto [totalSpace, freeSpace] = getStorageStats(p);

            if (totalSpace < 0 || freeSpace < 0) {
                LOG_ERROR("Failed to get stats for %s", p.c_str());
                continue;
            }

            totalMbytes += totalSpace;
        }

        // User partition stats
        const auto storagePath       = purefs::dir::getUserDiskPath();
        auto [totalSpace, freeSpace] = getStorageStats(storagePath);

        if (totalSpace < 0 || freeSpace < 0) {
            LOG_ERROR("Failed to get stats for %s", storagePath.c_str());
        }
        else {
            totalMbytes += totalSpace;
            freeUserMbytes = freeSpace;

            // Deduct 1024 MB reserved for OS data on User partition
            freeUserMbytes -= OS_RESERVED_SPACE_IN_MB;
        }

        if (totalMbytes) {
            freePercent = (freeUserMbytes * 100) / totalMbytes;
        }

        return {totalMbytes, freeUserMbytes, freePercent};
    }

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp => module-services/service-desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp +47 -0
@@ 0,0 1,47 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <endpoints/Endpoint.hpp>
#include <Service/Service.hpp>

#include <string>
#include <tuple>

#include <json11.hpp>

namespace sdesktop::endpoints
{
    class DeviceInfoEndpointCommon : public Endpoint
    {
      public:
        enum class DiagnosticFileType
        {
            Logs,
            CrashDumps
        };

        auto handleGet(Context &context) -> http::Code;
        auto gatherListOfDiagnostics(Context &context, DiagnosticFileType diagDataType) -> http::Code;
        auto parseDiagnosticFileType(const json11::Json &fileList) -> DiagnosticFileType;
        auto listDirectory(const std::string &path) -> std::vector<std::string>;
        auto fileListToJsonObject(const std::vector<std::string> &fileList) const -> json11::Json::object const;
        auto requestLogsFlush() const -> void;
        auto getStorageStats(const std::string &path) -> std::tuple<long, long>;
        auto getStorageInfo() -> std::tuple<long, long, long>;

        static constexpr auto OS_RESERVED_SPACE_IN_MB = (1024LU);

        explicit DeviceInfoEndpointCommon(sys::Service *ownerServicePtr) : Endpoint(ownerServicePtr)
        {
            debugName = "DeviceInfoEndpoint";
        }
        auto handle(Context &context) -> void override;
        virtual auto getDeviceInfo(Context &context) -> http::Code
        {
            return http::Code::BadRequest;
        };
    };

} // namespace sdesktop::endpoints

M products/BellHybrid/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp => products/BellHybrid/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp +5 -77
@@ 11,6 11,7 @@

#include <cstdint>
#include <string>
#include <vector>
#include <sys/statvfs.h>
#include <purefs/filesystem_paths.hpp>



@@ 19,80 20,9 @@
namespace sdesktop::endpoints
{

    auto DeviceInfoEndpoint::handle(Context &context) -> void
    auto DeviceInfoEndpoint::getDeviceInfo(Context &context) -> http::Code
    {
        switch (context.getMethod()) {
        case http::Method::get:
            getDeviceInfo(context);
            break;
        default:
            context.setResponseStatus(http::Code::BadRequest);
            sender::putToSendQueue(context.createSimpleResponse());
            break;
        }
    }

    auto DeviceInfoEndpoint::getSerialNumber() -> std::string
    {
        return dynamic_cast<ServiceDesktop *>(ownerServicePtr)->getSerialNumber();
    }

    auto DeviceInfoEndpoint::getStorageStats(const std::string &path) -> std::tuple<long, long>
    {
        std::unique_ptr<struct statvfs> vfstat = std::make_unique<struct statvfs>();
        if (statvfs(path.c_str(), vfstat.get()) < 0) {
            return {-1, -1};
        }

        unsigned long totalMbytes = (uint64_t(vfstat->f_blocks) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);
        unsigned long freeMbytes  = (uint64_t(vfstat->f_bfree) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);

        return {totalMbytes, freeMbytes};
    }

    auto DeviceInfoEndpoint::getDeviceInfo(Context &context) -> bool
    {
        if (ownerServicePtr == nullptr) {
            context.setResponseStatus(http::Code::InternalServerError);
            return false;
        }

        const std::array<std::filesystem::path, 2> storagePaths{purefs::dir::getRootDiskPath(),
                                                                purefs::dir::getPreviousOSPath()};

        unsigned long totalMbytes    = 0;
        unsigned long freeUserMbytes = 0;
        unsigned long freePercent    = 0;

        for (const auto &p : storagePaths) {
            auto [totalSpace, freeSpace] = getStorageStats(p);

            if (totalSpace < 0 || freeSpace < 0) {
                LOG_ERROR("Failed to get stats for %s", p.c_str());
                continue;
            }

            totalMbytes += totalSpace;
        }

        // User partition stats
        const auto storagePath       = purefs::dir::getUserDiskPath();
        auto [totalSpace, freeSpace] = getStorageStats(storagePath);

        if (totalSpace < 0 || freeSpace < 0) {
            LOG_ERROR("Failed to get stats for %s", storagePath.c_str());
        }
        else {
            totalMbytes += totalSpace;
            freeUserMbytes = freeSpace;

            // Deduct 1024 MB reserved for OS data on User partition
            freeUserMbytes -= OS_RESERVED_SPACE_IN_MB;
        }

        if (totalMbytes) {
            freePercent = (freeUserMbytes * 100) / totalMbytes;
        }
        auto [totalMbytes, freeUserMbytes, freePercent] = getStorageInfo();

        context.setResponseBody(
            json11::Json::object({{json::batteryLevel, std::to_string(Store::Battery::get().level)},


@@ 103,11 33,9 @@ namespace sdesktop::endpoints
                                  {json::gitRevision, (std::string)(GIT_REV)},
                                  {json::gitBranch, (std::string)GIT_BRANCH},
                                  {json::currentRTCTime, std::to_string(static_cast<uint32_t>(std::time(nullptr)))},
                                  {json::version, std::string(VERSION)},
                                  {json::serialNumber, getSerialNumber()}}));
                                  {json::version, std::string(VERSION)}}));

        sender::putToSendQueue(context.createSimpleResponse());
        return true;
        return http::Code::OK;
    }

} // namespace sdesktop::endpoints

M products/BellHybrid/services/desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpoint.hpp => products/BellHybrid/services/desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpoint.hpp +6 -17
@@ 4,28 4,17 @@
#pragma once

#include <endpoints/Endpoint.hpp>

#include <Service/Service.hpp>

#include <string>
#include <endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp>

namespace sdesktop::endpoints
{

    class DeviceInfoEndpoint : public Endpoint
    class DeviceInfoEndpoint : public DeviceInfoEndpointCommon
    {
        auto getSerialNumber() -> std::string;
        auto getStorageStats(const std::string &path) -> std::tuple<long, long>;

        static constexpr auto OS_RESERVED_SPACE_IN_MB = (1024LU);

      public:
        explicit DeviceInfoEndpoint(sys::Service *ownerServicePtr) : Endpoint(ownerServicePtr)
        {
            debugName = "DeviceInfoEndpoint";
        }
        auto handle(Context &context) -> void override;
        auto getDeviceInfo(Context &context) -> bool;
        explicit DeviceInfoEndpoint(sys::Service *ownerServicePtr) : DeviceInfoEndpointCommon(ownerServicePtr)
        {}

        auto getDeviceInfo(Context &context) -> http::Code override;
    };

} // namespace sdesktop::endpoints

M products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp => products/PurePhone/services/desktop/endpoints/deviceInfo/DeviceInfoEndpoint.cpp +3 -155
@@ 19,104 19,6 @@

namespace sdesktop::endpoints
{
    auto DeviceInfoEndpoint::handle(Context &context) -> void
    {
        switch (context.getMethod()) {
        case http::Method::get:
            handleGet(context);
            break;
        default:
            context.setResponseStatus(http::Code::BadRequest);
            break;
        }

        sender::putToSendQueue(context.createSimpleResponse());
    }

    auto DeviceInfoEndpoint::handleGet(Context &context) -> void
    {
        const auto &requestBody = context.getBody();

        if (requestBody[json::fileList].is_number()) {

            const auto diagFileType = static_cast<DiagnosticFileType>(requestBody[json::fileList].int_value());

            if (!magic_enum::enum_contains<DiagnosticFileType>(diagFileType)) {
                LOG_ERROR("Bad diagnostic type %d requested", static_cast<unsigned>(diagFileType));

                context.setResponseStatus(http::Code::BadRequest);
                return;
            }

            gatherListOfDiagnostics(context, diagFileType);
        }
        else {
            getDeviceInfo(context);
        }
    }

    auto DeviceInfoEndpoint::gatherListOfDiagnostics(Context &context, DiagnosticFileType diagDataType) -> void
    {
        std::vector<std::string> fileList;
        auto status = http::Code::NoContent;

        try {
            requestLogsFlush();
        }
        catch (const std::runtime_error &e) {
            LOG_ERROR("Logs flush exception: %s", e.what());
        }

        switch (diagDataType) {
        case DiagnosticFileType::Logs: {
            fileList = listDirectory(purefs::dir::getLogsPath());
            break;
        }

        case DiagnosticFileType::CrashDumps: {
            fileList = listDirectory(purefs::dir::getCrashDumpsPath());
            break;
        }
        }

        if (!fileList.empty()) {
            status = http::Code::OK;
            context.setResponseBody(fileListToJsonObject(fileList));
        }

        context.setResponseStatus(status);
    }

    auto DeviceInfoEndpoint::requestLogsFlush() const -> void
    {
        auto owner = dynamic_cast<ServiceDesktop *>(ownerServicePtr);
        if (owner) {
            owner->requestLogsFlush();
        }
    }

    auto DeviceInfoEndpoint::fileListToJsonObject(const std::vector<std::string> &fileList) const
        -> json11::Json::object const
    {
        json11::Json::array fileArray;

        for (const auto &file : fileList) {
            fileArray.push_back(file);
        }

        return json11::Json::object{{json::files, fileArray}};
    }

    auto DeviceInfoEndpoint::listDirectory(std::string path) -> std::vector<std::string>
    {
        std::vector<std::string> entries;

        for (const auto &entry : std::filesystem::directory_iterator(path)) {
            entries.push_back(entry.path());
        }

        return entries;
    }

    auto DeviceInfoEndpoint::getSerialNumber() -> std::string
    {


@@ 133,62 35,9 @@ namespace sdesktop::endpoints
        return static_cast<ServiceDesktop *>(ownerServicePtr)->getDeviceToken();
    }

    auto DeviceInfoEndpoint::getStorageStats(const std::string &path) -> std::tuple<long, long>
    auto DeviceInfoEndpoint::getDeviceInfo(Context &context) -> http::Code
    {
        std::unique_ptr<struct statvfs> vfstat = std::make_unique<struct statvfs>();
        if (statvfs(path.c_str(), vfstat.get()) < 0) {
            return {-1, -1};
        }

        unsigned long totalMbytes = (uint64_t(vfstat->f_blocks) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);
        unsigned long freeMbytes  = (uint64_t(vfstat->f_bfree) * uint64_t(vfstat->f_bsize)) / (1024LLU * 1024LLU);

        return {totalMbytes, freeMbytes};
    }

    auto DeviceInfoEndpoint::getDeviceInfo(Context &context) -> bool
    {
        if (ownerServicePtr == nullptr) {
            context.setResponseStatus(http::Code::InternalServerError);
            return false;
        }

        const std::array<std::filesystem::path, 2> storagePaths{purefs::dir::getRootDiskPath(),
                                                                purefs::dir::getPreviousOSPath()};

        unsigned long totalMbytes    = 0;
        unsigned long freeUserMbytes = 0;
        unsigned long freePercent    = 0;

        for (const auto &p : storagePaths) {
            auto [totalSpace, freeSpace] = getStorageStats(p);

            if (totalSpace < 0 || freeSpace < 0) {
                LOG_ERROR("Failed to get stats for %s", p.c_str());
                continue;
            }

            totalMbytes += totalSpace;
        }

        // User partition stats
        const auto storagePath       = purefs::dir::getUserDiskPath();
        auto [totalSpace, freeSpace] = getStorageStats(storagePath);

        if (totalSpace < 0 || freeSpace < 0) {
            LOG_ERROR("Failed to get stats for %s", storagePath.c_str());
        }
        else {
            totalMbytes += totalSpace;
            freeUserMbytes = freeSpace;

            // Deduct 1024 MB reserved for OS data on User partition
            freeUserMbytes -= OS_RESERVED_SPACE_IN_MB;
        }

        if (totalMbytes) {
            freePercent = (freeUserMbytes * 100) / totalMbytes;
        }
        auto [totalMbytes, freeUserMbytes, freePercent] = getStorageInfo();

        context.setResponseBody(json11::Json::object(
            {{json::batteryLevel, std::to_string(Store::Battery::get().level)},


@@ 212,7 61,6 @@ namespace sdesktop::endpoints
             {json::backupLocation, purefs::dir::getBackupOSPath().string()},
             {json::deviceToken, getDeviceToken()}}));

        context.setResponseStatus(http::Code::OK);
        return true;
        return http::Code::OK;
    }
} // namespace sdesktop::endpoints

M products/PurePhone/services/desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpoint.hpp => products/PurePhone/services/desktop/endpoints/include/endpoints/deviceInfo/DeviceInfoEndpoint.hpp +6 -21
@@ 4,6 4,7 @@
#pragma once

#include <endpoints/Endpoint.hpp>
#include <endpoints/deviceInfo/DeviceInfoEndpointCommon.hpp>

#include <Service/Service.hpp>



@@ 12,33 13,17 @@

namespace sdesktop::endpoints
{
    class DeviceInfoEndpoint : public Endpoint
    class DeviceInfoEndpoint : public DeviceInfoEndpointCommon
    {
      public:
        enum class DiagnosticFileType
        {
            Logs,
            CrashDumps
        };
        explicit DeviceInfoEndpoint(sys::Service *ownerServicePtr) : DeviceInfoEndpointCommon(ownerServicePtr)
        {}

        auto getSerialNumber() -> std::string;
        auto getCaseColour() -> std::string;
        auto handleGet(Context &context) -> void;
        auto gatherListOfDiagnostics(Context &context, DiagnosticFileType diagDataType) -> void;
        auto listDirectory(std::string path) -> std::vector<std::string>;
        auto fileListToJsonObject(const std::vector<std::string> &fileList) const -> json11::Json::object const;
        auto requestLogsFlush() const -> void;
        auto getStorageStats(const std::string &path) -> std::tuple<long, long>;
        auto getDeviceToken() -> std::string;

        static constexpr auto OS_RESERVED_SPACE_IN_MB = (1024LU);

      public:
        explicit DeviceInfoEndpoint(sys::Service *ownerServicePtr) : Endpoint(ownerServicePtr)
        {
            debugName = "DeviceInfoEndpoint";
        }
        auto handle(Context &context) -> void override;
        auto getDeviceInfo(Context &context) -> bool;
        auto getDeviceInfo(Context &context) -> http::Code override;
    };

} // namespace sdesktop::endpoints

M test/get_os_log.py => test/get_os_log.py +14 -2
@@ 7,11 7,21 @@ import os
import atexit

from harness.harness import Harness
from harness.request import TransactionError
from harness.interface.error import TestError, Error
from harness.interface.defs import status
from harness.api.developermode import PhoneModeLock
from harness.api.security import GetPhoneLockStatus
from harness.api.filesystem import get_log_file, get_log_file_with_path
from harness.api.device_info import *

def is_phone_locked(harness: Harness):
    try:
        GetPhoneLockStatus().run(harness)
    except TransactionError as error:
        return error.status == status["Forbidden"]
    else:
        return False

def set_passcode(harness: Harness, flag: bool):
    '''


@@ 47,8 57,10 @@ def main():

    harness = Harness.from_detect()

    atexit.register(set_passcode, harness, True)
    set_passcode(harness, False)
    if is_phone_locked(harness):
        atexit.register(set_passcode, harness, True)
        set_passcode(harness, False)

    get_log_files(harness, DiagnosticsFileList.LOGS, log_save_dir)
    get_log_files(harness, DiagnosticsFileList.CRASH_DUMPS, log_save_dir)
    exit(0)