/*! * * Copyright (c) 2021-2025 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file DeviceController.cpp * \author (last) Behrouz NematiPour * \date (last) 31-Jan-2025 * \author (original) Behrouz NematiPour * \date (original) 03-Jun-2021 * */ #include "DeviceController.h" // Linux #include #include // Qt #include #include #include // Project #include "Threads.h" #include "StorageGlobals.h" #include "Logger.h" #include "CloudSyncController.h" #include "ApplicationController.h" #include "FileHandler.h" #include "DeviceModels.h" #include "Settings.h" #include "encryption.h" // namespace using namespace Model; using namespace Device; using namespace Storage; /*! * \brief DeviceController::DeviceController * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ DeviceController::DeviceController(QObject *parent) : QObject(parent) { _fileSystemWatcher.setParent(this); DEVICE_DEV_PARENT_LIST } /*! * \brief DeviceController::init * \details Initializes the class by setting the connections and starting the timer * \return False if it has been called before. */ bool DeviceController::init() { if ( _init ) return false; _init = true; // runs in DeviceController thread initConnections(); startTimer(_interval); return true; } /*! * \brief DeviceController::init * \details Initialized the Class by calling the init() method first * And initializes the thread vThread by calling initThread * on success init(). * \param vThread - the thread * \return returns the return value of the init() method */ bool DeviceController::init(QThread &vThread) { if ( ! init() ) return false; initThread(vThread); return true; } /*! * \brief DeviceController::quit * \details quits the class * Calls quitThread */ void DeviceController::quit() { quitThread(); } /*! * \brief DeviceController::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. */ void DeviceController::initConnections() { connect(&_ApplicationController , SIGNAL(didUSBDriveUmount()), this , SLOT( onUSBDriveUmount())); connect(this , SIGNAL(didScreenshot(const QImage &, const QString &)), this , SLOT( onScreenshot(const QImage &, const QString &))); connect(&_fileSystemWatcher , SIGNAL( fileChanged(const QString &)), this , SLOT( onWatchFileChanged(const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTOSVersionData (const QString &)), this , SLOT( onPOSTOSVersionData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTEthernetData (const QString &)), this , SLOT( onPOSTEthernetData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTWirelessData (const QString &)), this , SLOT( onPOSTWirelessData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTBluetoothData (const QString &)), this , SLOT( onPOSTBluetoothData (const QString &))); connect(&_ApplicationController , SIGNAL(didPOSTCloudSyncData (const QString &)), this , SLOT( onPOSTCloudSyncData (const QString &))); connect(&_Logger , SIGNAL(didLogBackup (const QString &)), this , SLOT( onLogBackup (const QString &))); connect(&_CloudSyncController , SIGNAL(didLogUpload (const QString &)), this , SLOT( onLogUpload (const QString &))); DEVICE_DEV_INIT_CONNECTIONS_LIST connect(this, SIGNAL(didEventThreadChange()), this, SLOT( onEventThreadChange())); } /*! * \brief ApplicationController::initThread * \details Moves this object into the thread vThread. * And checks that this method is called from main thread. * Also connects quitThread to application aboutToQuit. * \param vThread - the thread */ void DeviceController::initThread(QThread &vThread) { // runs in main thread Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); _thread = &vThread; _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); _thread->start(); moveToThread(_thread); emit didEventThreadChange( QPrivateSignal() ); } /*! * \brief DeviceController::quitThread * \details Moves this object to main thread to be handled by QApplication * And to be destroyed there. */ void DeviceController::quitThread() { // disabled coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. if ( ! _thread ) return; // runs in thread moveToThread(qApp->thread()); // validated } // disabled coco end /*! * \brief DeviceController::usbSeek * \details Tries to look for the available USB devices * Starts from sda1 to sdz1. * \note will only look for the first partition if there is any * \param vDevice - Found device (/dev/sda1) * \return true if a device found */ bool DeviceController::usbSeek(QString &vDevice) { // disabled coco begin validated: Needed User Interaction so tested manually // This function cannot be tested on any device other than target // because only on device the usb is /dev/sdX# and others are mmcblk___ // but on VM and normal Linuxes all drives are /dev/sdX# and cannot be identified as USB. // And the difference between how USB is detected and sd-card is that // for the USB we need to look into /dev/sdX# // but for the sd-card we look for the mounted path which is much easier. // That's why indication of the sd-card is more accurate than USB at least on VM. QString dev = "/dev/sd"; QString device = ""; for (char a = 'a'; a <= 'z'; a++) { device = dev + a + '1'; if (QFileInfo::exists(device)) { vDevice = device; return true; // application is deciding on the first existing drive } } vDevice = device; return false; // disabled coco end } /*! * \brief DeviceController::driveSpaceCheck * \param vPath - Device mount point * \param vTotalBytes - Total volume size in bytes * \param vAvailableBytes - Size (in bytes) available for the current user * \param vPercent - The percentage of available space * \return - The drive mounted and ready * \note if device ejected manually system assumes it's still ready. */ bool DeviceController::driveSpaceCheck(const QString &vPath, qint64 &vTotalBytes, qint64 &vAvailableBytes, bool *vIsReadOnly) { QStorageInfo storage(vPath); bool isReady = storage.isReady (); bool isReadOnly = storage.isReadOnly(); bool isMounted = FileHandler::isMounted(vPath); if ( vIsReadOnly ) *vIsReadOnly = isReadOnly; if ( isReady ) { vTotalBytes = storage.bytesTotal(); vAvailableBytes = storage.bytesAvailable(); } if ( ! isMounted || ! isReady ) { isReady = false; vTotalBytes = 0; vAvailableBytes = 0; } return isReady; } /*! * \brief DeviceController::timerEvent * \details This event handler has been re-implemented in here * to receive timer events for the object * for the timer which has been set to _checkInterval * Runs the usbCheck on interval */ void DeviceController::timerEvent(QTimerEvent *) { #ifdef BUILD_FOR_TARGET usbCheck(); sdcardSpaceCheck(); // The treatment logs are held in a separate partition from the unencrypted // logs and need a separate disk space usage check settingsPartitionSpaceCheck(); #endif findPendingLogs(); } /*! * \brief DeviceController::usbCheck * \details Runs usbSeek to mount or umount or remove it * regarding the state it's in. */ void DeviceController::usbCheck() { QString device = ""; usbSpaceCheck(); if (usbSeek(device)) { if (! _umounted ) { // avoid to mount the USB which has just been unmounted if (! _mounted ) { // avoid to mount the USB which has just been mounted usbMount(device); } } else { // the umount is requested? usbUmount(USB_Mount_Point); } } else { if ( ! _removed ) { usbRemove(); } } } /*! * \brief DeviceController::sdcardSpaceCheck * \details Checks for the SD-Card drive space. */ void DeviceController::sdcardSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; static quint8 mOPercent = 0; quint8 mCPercent = 0; qint64 mCTotal = 0; bool isMounted = FileHandler::isMounted(Storage::SDCard_Base_Path_Name); QString pathToCheckSpace = isMounted ? Storage::SDCard_Base_Path_Name : gStandard_tmp; mCIsReady = driveSpaceCheck(pathToCheckSpace, mCTotal, mCAvailable, &mCIsReadOnly); //DEBUG: qDebug()<< "Checking space for path of : " << pathToCheckSpace << " mCTotal " << mCTotal << " available " << mCAvailable; if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; //DEBUG:0: qDebug() << " ~~~~~~~~~~ " << __FUNCTION__ << mInitialized << mCIsReady << mOIsReady << mCIsReadOnly << mOIsReadOnly; emit didSDCardStateChange(mCIsReady, mCIsReadOnly); } //NOTE: this if block has to be independent of the mOIsReady != mCIsReady // because current and old may be the same all the time and then this if block will not execute // and reaches to the log and fills the log unnecessarily. if (! mCIsReady ) { mOPercent = 0; mOAvailable = 0; emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); return; } mCPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mCPercent < _minRequiredAvailableSpacePercent) { LOG_DEBUG(QString("SD-CARD space lower than %1%").arg(_minRequiredAvailableSpacePercent)); emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); emit didSDCardSpaceTooLow(_minRequiredAvailableSpacePercent); } //DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mOAvailable << mCAvailable << (mOAvailable == mCAvailable) << mOPercent << mCPercent << mCIsReadOnly; if (mOPercent != mCPercent || mOAvailable != mCAvailable ) { mOPercent = mCPercent ; mOAvailable = mCAvailable ; emit didSDCardSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mCAvailable << mPercent ; } } /*! * \brief DeviceController::usbSpaceCheck * \details Checks for the USB drive space. */ void DeviceController::usbSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOTotal = 0; qint64 mCTotal = 0; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; quint8 mPercent = 0; mCIsReady = driveSpaceCheck(Storage::USB_Mount_Point, mCTotal, mCAvailable, &mCIsReadOnly); #if BUILD_FOR_DESKTOP mCIsReady = true; // it is set to always true since on desktop a local folder is used for the USB folder which doesn't need (un)mount. #endif if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; emit didUSBStateChange(mCIsReady, mCIsReadOnly); if (! mCIsReady ) { mOTotal = 0; mOAvailable = 0; emit didUSBSpaceChange(mCIsReady, mCTotal, mCAvailable, mPercent); return; } } mPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mOTotal != mCTotal || mOAvailable != mCAvailable) { mOTotal = mCTotal ; mOAvailable = mCAvailable ; emit didUSBSpaceChange(mCIsReady, mCTotal, mCAvailable, mPercent); } // DEBUG: qDebug() << "DeviceController::usbSpaceCheck" // << mCIsReady // << mCIsReadOnly // << mCTotal // << mCAvailable ; } /*! * \brief DeviceController::settingsPartitionSpaceCheck * \details Checks the disk space of the encrypted partition */ void DeviceController::settingsPartitionSpaceCheck() { static bool mInitialized = false; // Old Info ; // Current info static bool mOIsReady = false; bool mCIsReady = false; static bool mOIsReadOnly = false; bool mCIsReadOnly = false; static qint64 mOAvailable = 0; qint64 mCAvailable = 0; static quint8 mOPercent = 0; quint8 mCPercent = 0; qint64 mCTotal = 0; mCIsReady = driveSpaceCheck(Storage::Settings_Path(), mCTotal, mCAvailable, &mCIsReadOnly); //DEBUG: qDebug()<< "Checking space for path of : " << Storage::Settings_Path() << " mCTotal " << mCTotal << " available " << mCAvailable; if (mOIsReadOnly != mCIsReadOnly || mOIsReady != mCIsReady || ! mInitialized ) { mOIsReadOnly = mCIsReadOnly; mOIsReady = mCIsReady; mInitialized = true; //DEBUG:0: qDebug() << " ~~~~~~~~~~ " << __FUNCTION__ << mInitialized << mCIsReady << mOIsReady << mCIsReadOnly << mOIsReadOnly; emit didSettingsPartitionStateChange(mCIsReady, mCIsReadOnly); } //NOTE: this if block has to be independent of the mOIsReady != mCIsReady // because current and old may be the same all the time and then this if block will not execute // and reaches to the log and fills the log unnecessarily. if (! mCIsReady ) { mOPercent = 0; mOAvailable = 0; emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); return; } mCPercent = mCTotal ? ((100 * mCAvailable) / mCTotal) : 0; if (mCPercent < _minRequiredAvailableSpacePercent) { LOG_DEBUG(QString("Settings partition space lower than %1%").arg(_minRequiredAvailableSpacePercent)); emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); emit didSDCardSpaceTooLow(_minRequiredAvailableSpacePercent); } /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mOTotal << mCTotal << (mOTotal == mCTotal) << mOAvailable << mCAvailable << (mOAvailable == mCAvailable) << mPercent << mCIsReadOnly; if (mOPercent != mCPercent && mOAvailable != mCAvailable ) { mOPercent = mCPercent ; mOAvailable = mCAvailable ; emit didSettingsPartitionSpaceChange(mCIsReady, mCTotal, mCAvailable, mCPercent); /// DEBUG: qDebug() << Storage::SDCard_Base_Path_Name << mCIsReady << mCTotal << mCAvailable << mPercent ; } } /*! * \brief DeviceController::usbError * \details Logs any error which has been happened * On USB device vDevice * \note When this method has been called error number will be read from errno variable, * Which has been set by umount or mount. * \param vDevice */ void DeviceController::usbError(const QString &vDevice) { // disabled coco begin validated: This needs user interaction to plug-in/out the USB device // has been tested manually QString error; static QString lastError; switch (errno) { case EBUSY: error = tr("%1 - Device or resource busy (%2)").arg(errno).arg(vDevice); _mounted = true; break; default: error = tr("%1 - %2 (%3 , %4)").arg(errno).arg(strerror(errno)).arg(vDevice).arg(USB_Mount_Point); break; } if (error != lastError) { LOG_DEBUG("USB: " + error); lastError = error; } } // disabled coco end /*! * \brief DeviceController::onUSBDriveUmount * \details This is the slot connected to the _ApplicationController's didUSBDriveUmount SIGNAL, * To notify the USB drive detach. */ void DeviceController::onUSBDriveUmount() { // disabled coco begin validated: This needs user interaction to plug-in/out the USB device // has been tested manually _umounted = true; } // disabled coco end /*! * \brief DeviceController::usbMount * \details Mounts the USB device vDevice * \note Emits didUSBDriveMount signal * \param vDevice - USB device to be mounted (e.g. /dev/sda1) * \return true on successful mount */ void DeviceController::usbMount(const QString &vDevice) { usbMountReq(vDevice); } /*! * \brief DeviceController::usbUmount * \details Unmounts the USB device vDevice * \note Emits didUSBDriveUmount signal * \param vDevice - USB device to be unmounted (e.g. /dev/sda1) * \return true on successful unmount */ void DeviceController::usbUmount(const QString &vDevice) { usbMountReq(vDevice, false); } /*! * \brief DeviceController::usbRemove * \details Removed the USB mount point * So next time it is not mounted as next device. * \note Emits didUSBDriveRemove signal */ void DeviceController::usbRemove() { // disabled coco begin validated: This needs user interaction to plug-out the USB device // has been tested manually usbUmount(USB_Mount_Point); _umounted = false; _removed = true; LOG_DEBUG("USB drive removed"); emit didUSBDriveRemove(); } // disabled coco end /*! * \brief DeviceController::checkError * \details check if has error, then sets the base model accept to false and the reason to the error. * in that case logs the error message and emit the didAttributeResponse to notify the GUI. * \param vError - the error code, this can be the Gui enum or system number * \param vExtraLogInfo - any extra information to be logged. Not display to user. * \return */ template bool DeviceController::checkError(DeviceError::Scripts_Error_Enum vError, TModel &vModel, QString vExtraLogInfo) { if ( vError ) { QString src = (vError > DeviceError::eDevice_Scripts_Error_Start ? MAbstract::unitText(MAbstract::Unit_Enum::eUI) : MAbstract::unitText(MAbstract::Unit_Enum::eDV)) + ","; vModel._data.mAccepted = false ; vModel._data.mReason = vError ; vModel._data.mMessage = DeviceError::deviceErrorText(vError, vError); LOG_APPED(" ," + src + vModel._data.mMessage + " " + vExtraLogInfo); emit didAttributeResponse(vModel._data); return true; } return false; } ///////////////////////////////////////////// DeviceBrightness /*! * \brief DeviceController::onAttributeRequest * \details Sets the brightness level * \param vBrightness */ void DeviceController::onAttributeRequest(const DeviceBrightnessRequestData &vData) { // ----- initializing the member variable models _deviceBrightnessRequest ._data = vData; // ----- extract the required data _deviceBrightnessRequest.setBrightnessSysVal(); LOG_APPED( _deviceBrightnessRequest.toString()); // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, vData.mRead ? Brightness_Get : Brightness_Set), _deviceBrightnessResponse, script) ) return; // ----- check if the process is not running if ( _processBrightness.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceBrightnessResponse); return; } // ----- run the process int timeout_ms = 10000; QStringList params; params << QString::number(_deviceBrightnessRequest._data.mBrightness_val); TimedProcess *timedProcess = new TimedProcess(&_processBrightness, script, timeout_ms, params); timedProcess->start(); } /*! * \brief DeviceController::onProcessBrightnessFinished * \details Called when the process to set the brightness has finished * \param vExitCode (int) the exit code * \note exit code -> 0 : set Accept [MBase] -> Log -> emit * !0 : set Attrib [MBrgh] -> Log -> emit * 1 - get an error when in onAttributeRequest : scriptErrorText([Gui Enum ]) * 2 - get an error when in onProcessBrightnessExitCode : scriptErrorText([vExitCode]) * 3 - get no error when in onProcessBrightnessExitCode : MDeviceResponse.toString() * - in case 3 the specific model _data has to be filled prior to the toString to have it in the log. */ void DeviceController::onProcessBrightnessExitCode(int vExitCode, QProcess::ExitStatus) { if ( ! checkError(static_cast(vExitCode), _deviceBrightnessResponse, _deviceBrightnessResponse.toString()) ) { // has no error if (_deviceBrightnessRequest._data.mRead) { bool ok = false; int brightness = _processBrightness.readLine().toInt(&ok); if (ok) { _deviceBrightnessResponse.setBrightnessPercent(brightness); } else { checkError(DeviceError::eDevice_Scripts_Error_Incorrect_Rsp,_deviceBrightnessResponse, _deviceBrightnessResponse.toString()); return; } } else { _deviceBrightnessResponse.setBrightnessPercent(_deviceBrightnessRequest._data.mBrightness_val); _deviceBrightnessResponse._data.mMessage = _deviceBrightnessResponse.toString(); } LOG_APPED(_deviceBrightnessResponse._data.mMessage); emit didAttributeResponse(_deviceBrightnessResponse._data); } } ///////////////////////////////////////////// DeviceRootSSHAccess /*! * \brief DeviceController::onAttributeRequest * \details Sets the RootSSHAccess * \param vRootSSHAccess */ void DeviceController::onAttributeRequest(const DeviceRootSSHAccessRequestData &vData) { // ----- initializing the member variable models _deviceRootSSHAccessRequest._data = vData; LOG_APPED( _deviceRootSSHAccessRequest.toString()); // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, RootSSHAccess), _deviceRootSSHAccessResponse, script) ) return; // ----- check if the process is not running if ( _processRootSSHAccess.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceRootSSHAccessResponse); return; } // ----- run the process int timeout_ms = 10000; QStringList params; if ( ! _deviceRootSSHAccessRequest._data.mIsGet ) params << FSN(_deviceRootSSHAccessRequest._data.mRootSSHAccess); TimedProcess *timedProcess = new TimedProcess(&_processRootSSHAccess, script, timeout_ms, params); timedProcess->start(); } /*! * \brief DeviceController::onProcessRootSSHAccessExitCode * \details Called when the process to set the RootSSHAccess has finished * \param vExitCode (int) the exit code * \note exit code -> 0 : set Accept [MBase] -> Log -> emit * !0 : set Attrib [MBrgh] -> Log -> emit * 1 - get an error when in onAttributeRequest : scriptErrorText([Gui Enum ]) * 2 - get an error when in onProcessRootSSHAccessExitCode : scriptErrorText([vExitCode]) * 3 - get no error when in onProcessRootSSHAccessExitCode : MDeviceResponse.toString() * - in case 3 the specific model _data has to be filled prior to the toString to have it in the log. */ void DeviceController::onProcessRootSSHAccessExitCode(int vExitCode, QProcess::ExitStatus) { if ( ! checkError(static_cast(vExitCode), _deviceRootSSHAccessResponse, _deviceRootSSHAccessResponse.toString()) ) { // has no error if (_deviceRootSSHAccessRequest._data.mIsGet) { bool ok = true; Qt::CheckState rootSSHAccess = Qt:: Unchecked; uint value = _processRootSSHAccess.readLine().toUInt(&ok); if ( ! ok ) goto lError; switch (value) { case 0 : rootSSHAccess = Qt:: Unchecked; break; case 1 : rootSSHAccess = Qt::PartiallyChecked; break; case 2 : rootSSHAccess = Qt:: Checked; break; default : ok = false; } if ( ! ok ) goto lError; _deviceRootSSHAccessResponse._data.mMessage = _deviceRootSSHAccessResponse.toString(); _deviceRootSSHAccessResponse._data.mRootSSHAccess = rootSSHAccess; } else { _deviceRootSSHAccessResponse._data.mMessage = _deviceRootSSHAccessResponse.toString(); _deviceRootSSHAccessResponse._data.mRootSSHAccess = _deviceRootSSHAccessRequest._data.mRootSSHAccess; } LOG_APPED(_deviceRootSSHAccessResponse._data.mMessage); emit didAttributeResponse(_deviceRootSSHAccessResponse._data); } else { // the error in this case is handled in the checkError } return; lError: checkError(DeviceError::eDevice_Scripts_Error_Incorrect_Rsp, _deviceRootSSHAccessResponse, _deviceRootSSHAccessResponse.toString()); } ///////////////////////////////////////////// DeviceCryptSetup /*! * \brief DeviceController::onAttributeRequest * \details Calls the CryptSetup script with the model data DeviceCryptSetupRequestData * \param vData - the model data */ void DeviceController::onAttributeRequest(const DeviceCryptSetupRequestData &vData) { //DEBUG qDebug() << " ---------- " << vData.mCommand << vData.mPassword; _deviceCryptSetupRequest._data = vData; // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Crypt_Setup), _deviceCryptSetupResponse, script) ) return; // ----- check if the process is not running if ( _processCryptSetup.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceCryptSetupResponse); return; } // ----- run the process int timeout = 10000; TimedProcess *timedProcess = new TimedProcess(&_processCryptSetup, script, timeout, { _deviceCryptSetupRequest._data.mCommand }); _processCryptSetup.setEnvironment(QProcess::systemEnvironment() << QString("PASSWORD=%1").arg(_deviceCryptSetupRequest._data.mPassword)); timedProcess->start(); // Update UI with a response MDeviceCryptSetupResponse model; model._data.mAccepted = false; model._data.mMessage = tr("Encrypted Partition %1 started.").arg(_deviceCryptSetupRequest._data.mCommand); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::onProcessCryptSetupExitCode * \param vExitCode * \param vStatus */ void DeviceController::onProcessCryptSetupExitCode(int vExitCode, QProcess::ExitStatus vStatus) { const int ERR_CRYPTSETUP_MOUNT_ISMOUNT=134; // is used in crypt_setup.sh do not modify // The Exit code in this script is not used. // any other checking is done by UI Software at the moment this script is called. // The only thing matters is the pared device info in text and it will be empty string if error happens. MDeviceCryptSetupResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processCryptSetup.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); // DEBUG: qDebug() << model._data.mEchoInfo; emit didAttributeResponse(model.data()); LOG_APPED_UI(model.data().mMessage); bool isSetup = _deviceCryptSetupRequest._data.mCommand == "setup"; bool isMount = _deviceCryptSetupRequest._data.mCommand == "mount"; bool isMounted = isMount && ( vExitCode == ERR_CRYPTSETUP_MOUNT_ISMOUNT || // is already mounted vExitCode == 0 // successful mount ); bool isUpdate = gEnableUpdating && isMounted; if ( isMount ) emit didCryptSetupMount(model._data.mAccepted); QString msg = ""; int err = 0 ; //TODO The Settings shall be the Singleton SettingsController and modify the MSettings like the others. Storage::Settings settings; // moving the configuration files if the encrypted partition creation was successful. if ( isUpdate ) goto lMove; // if it is gEnableUpdating, bypass the mAccepted for already mounted. if ( ! model._data.mAccepted ) goto lErr ; // any other case goto error if ( ! isSetup ) goto lOut ; // if not setup do NOT continue to move configurations lMove: err = settings.configurationsMove(&msg, isUpdate); if ( err ) { model._data.mAccepted = false ; model._data.mReason = err ; model._data.mMessage = msg ; emit didAttributeResponse(model.data()); LOG_APPED_UI(model.data().mMessage); } lOut: return; lErr: LOG_DEBUG(QString("Encrypted Partition %1 failed").arg(_deviceCryptSetupRequest._data.mCommand)); } ///////////////////////////////////////////// DeviceBluetoothPaired void DeviceController::onAttributeRequest(const DeviceBluetoothPairedResetRequestData &) { // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Bluetooth_Paired_Reset), _deviceBluetoothPairedResetResponse, script) ) return; // ----- check if the process is not running if ( _processBluetoothPairedReset.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceBluetoothPairedResetResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processBluetoothPairedReset, script, timeout_ms); timedProcess->start(); } void DeviceController::onProcessBluetoothPairedResetExitCode(int vExitCode, QProcess::ExitStatus vStatus) { // TODO: review the usage and definition of this object _deviceBluetoothPairedResetResponse. do we need it any more? MDeviceBluetoothPairedResetResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processBluetoothPairedReset.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); LOG_APPED_UI(model.data().mMessage); emit didAttributeResponse(model.data()); } ///////////////////////////////////////////// DeviceBluetoothPairedQuery void DeviceController::onAttributeRequest(const DeviceBluetoothPairedQueryRequestData &) { // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Bluetooth_Paired_Query), _deviceBluetoothPairedQueryResponse, script) ) return; // ----- check if the process is not running if ( _processBluetoothPairedQuery.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceBluetoothPairedQueryResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processBluetoothPairedQuery, script, timeout_ms); timedProcess->start(); } void DeviceController::onProcessBluetoothPairedQueryExitCode(int vExitCode, QProcess::ExitStatus vStatus) { // The Exit code in this script is not used. // any other checking is done by UI Software at the moment this script is called. // The only thing matters is the pared device info in text and it will be empty string if error happens. MDeviceBluetoothPairedQueryResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processBluetoothPairedQuery.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); emit didAttributeResponse(model.data()); LOG_APPED_UI(model.data().mMessage); } /*! * \brief DeviceController::doScreenshot * \details emit the screenshot signal to run that in Device controller thread * \param vCurrentDateTime */ void DeviceController::doScreenshot(const QImage &vImage, const QString &vFileName) { emit didScreenshot(vImage, vFileName); } /*! * \brief DeviceController::onScreenshot * \details The function to save the image vImage in the file vFileName * \param vImage - The image source object * \param vFileName - The filename to same the image to */ void DeviceController::onScreenshot(const QImage &vImage, const QString &vFileName) { vImage.save(vFileName); LOG_DEBUG("Screenshot saved in " + vFileName); } /*! * \brief DeviceController::ondoAddWatch * \details The thread safe add file watch method * \param vFile - The file to add to watch. */ void DeviceController::ondoAddWatch(const QString &vFile, bool vCreate) { DeviceError::Scripts_Error_Enum err = DeviceError::eDevice_OK; if ( vCreate ) { if ( ! FileHandler::write ( vFile, "", false) ) { err = DeviceError::eDevice_Watch_Error_NotCreate; goto lErr; }} else { if ( ! QFileInfo::exists ( vFile ) ) { err = DeviceError::eDevice_Watch_Error_NotFound ; goto lErr; }} if ( ! _fileSystemWatcher.removePath ( vFile ) ) { LOG_APPED_UI(QString("Device NOT watching %1").arg(vFile)); } else { LOG_APPED_UI(QString("Device watch removed %1").arg(vFile)); } if ( ! _fileSystemWatcher.addPath ( vFile ) ) { err = DeviceError::eDevice_Watch_Error_NotAdded ; goto lErr; } LOG_APPED_UI(QString("Device watch %1").arg(vFile)); return; lErr: LOG_DEBUG(DeviceError::deviceErrorText(err, 0)); } /*! * \brief DeviceController::onWatchFileChanged * \details This slot is called once the files being watched is updated. * \param vFile - the file name. */ void DeviceController::onWatchFileChanged(const QString &vFile) { emit didWatchFileChange(vFile); } /*! * \brief DeviceController::onEventThreadChange * \details The signal handler for the DeviceController(this)::didEventThreadChange * to start checking for the Encrypted partition readiness. */ void DeviceController::onEventThreadChange() { //DEBUG qDebug() << " ---------- " << __FUNCTION__ << QThread::currentThread()->objectName() << QThread::currentThread() << qApp->thread(); if ( QThread::currentThread() != &Threads::_DeviceController_Thread ) { qCritical() << " ***** Device controller thread not initialized correctly ***** "; return; } _hasThread = true; checkConfugurationMountReady(); } /*! * \brief DeviceController::onPOSTOSVersionData * \details Collects the OS Version * when it is ready after the POST is done reading OS Version * \param vMacAddress - The Ethernet MAC address */ void DeviceController::onPOSTOSVersionData(const QString &vOSVersion) { _osVersion = vOSVersion; emit didPOSTOSVersionData (vOSVersion); } /*! * \brief DeviceController::onPOSTEthernetData * \details Collects the ethernet mac address * when it is ready after the POST is done for the Ethernet * \param vMacAddress - The Ethernet MAC address */ void DeviceController::onPOSTEthernetData(const QString &vMacAddress) { _macEthernet = vMacAddress; emit didPOSTEthernetData (vMacAddress); } /*! * \brief DeviceController::onPOSTWirelessData * \details Collects the wireless mac address * when it is ready after the POST is done for the Wireless connection * \param vMacAddress - The Wireless MAC address */ void DeviceController::onPOSTWirelessData(const QString &vMacAddress) { _macWireless = vMacAddress; encryption::varSalt(vMacAddress); _hasSalt = ! vMacAddress.trimmed().isEmpty(); checkConfugurationMountReady(); emit didPOSTWirelessData (vMacAddress); } /*! * \brief DeviceController::onPOSTBluetoothData * \details Collects the bluetooth mac address * when it is ready after the POST is done for the Bluetooth * \param vMacAddress - The Bluetooth MAC address */ void DeviceController::onPOSTBluetoothData(const QString &vMacAddress) { _macBluetooth = vMacAddress; emit didPOSTBluetoothData (vMacAddress); } /*! * \brief DeviceController::onPOSTCloudSyncData * \details Collects the CloudSync Network Address * when it is ready after the POST is done for the CloudSync * \param vNetAddress - *** Not defined yet and is a placeholder for later use *** */ void DeviceController::onPOSTCloudSyncData(const QString &vNetAddress) { _netCloudSync = vNetAddress; emit didPOSTCloudSyncData (vNetAddress); } bool DeviceController::logBackup(const QString &vFileName) { if ( ! gLogUpload ) return false; // no log backup bool ok = true; QString fileSrc = vFileName; QFileInfo fileInfo(vFileName); QString filePath(fileInfo.absolutePath()); QString fileBase(fileInfo.baseName()); QString fileSufx(fileInfo.completeSuffix().prepend(_Logger.logFileNamePendingSubExt())); QString fileDest(QString("%1/%2.%3").arg(filePath, fileBase, fileSufx)); QString gzipSufx(gLogCompress ? Storage::gzipExt : ""); bool isBootPOST = _Logger.isBootPOST( vFileName ); bool isCurrent = fileInfo.lastModified().date() == QDate::currentDate(); if ( isBootPOST && isCurrent ) { // no log backup if it is the BootPOST log file, only rename and add the serial to it. fileDest = QString("%1/%2.%3").arg(filePath, fileBase.prepend(_Logger.logFileNameHDSN() + _Logger.fileSeparator()), fileInfo.completeSuffix()); ok = FileHandler::append(fileSrc, fileDest, true); return false; } if ( gLogCompress ) { ok = FileHandler::backupFile(fileSrc); } ok = QFile::rename(fileSrc + gzipSufx, fileDest + gzipSufx); // Now the serial received and the system is logging in the log with the serial number, // therefore it is not bootPOST. // Now try to back up the skipped bootPOST log. if ( ! isBootPOST ) { fileSrc .replace( _Logger.logFileNameHDSN(), _Logger.logFileNameHDSN() + _Logger.fileSeparator() + _Logger.logFileNameHDSN_default() ); fileDest.replace( _Logger.logFileNameHDSN(), _Logger.logFileNameHDSN() + _Logger.fileSeparator() + _Logger.logFileNameHDSN_default() ); if ( ! QFile::exists(fileSrc) ) goto lOut; ok = FileHandler::backupFile(fileSrc); ok = QFile::rename(fileSrc + gzipSufx, fileDest + gzipSufx); } lOut: return ok; } void DeviceController::onLogBackup(const QString &vFileName) { if ( ! gLogUpload ) return; // no log backup ( slot ) bool ok = true; ok = logBackup( vFileName); emit didLogBackup(ok, vFileName); } bool DeviceController::logUpload(const QString &vFileName) { if ( ! gLogUpload ) return false; // no log Uploaded rename bool ok = true; QFileInfo fileInfo(vFileName); const QString filePath; const QString fileBase(fileInfo.baseName()); const QString pendingExt = _Logger.logFileNamePendingSubExt(); const QString uploadExt = _Logger.logFileNameUploadedSubExt(); const QString fileSufx(fileInfo.completeSuffix().replace(pendingExt, uploadExt)); const QString fileDest = QString("%1.%2").arg(fileBase, fileSufx); Logger::LogType logType = _Logger.logFileLogType(vFileName, filePath); ok = logType != Logger::eLogNone; if ( ! ok ) { LOG_APPED_UI(QString("CS Incorrect log upload type [%1]").arg(fileSufx)); goto lOut; } // DEBUG qDebug() << __FUNCTION__ << "\n" << vFileName << "\n" << fileDest; ok = QFile::rename(filePath + vFileName, filePath + fileDest); lOut: return ok; } void DeviceController::onLogUpload(const QString &vFileName) { if ( ! gLogUpload ) return; // no log uploaded rename ( slot ) bool ok = true; ok = logUpload( vFileName); emit didLogUpload(ok, vFileName); } /*! * \brief DeviceController::checkConfugurationMountReady * \details Cheks if the system is ready to mount the encrypted partition. * The object has to be on its own thread and the salt for the decryption has to be ready. */ void DeviceController::checkConfugurationMountReady() { /// in manufacturing or updating the system is logged with root and configurations are in /home/root /// therefore no need to mount the cononfiguraiton partition. /// also for manufacturing the partition is being set up /// and is less steps for setup if the partition is not mounted. if ( gEnableManufacturing ) return; // it should do the mount when gEnableUpdating, therefore here should not add gEnableUpdating. if ( ! ( _hasThread && _hasSalt ) ) return; DeviceCryptSetupRequestData data; data.mCommand = "mount"; bool ok = false; data.mPassword = encryption::configurationsPassword( ok ); if ( ! ok ) { // not enough infromation to create a secure passowrd // status(tr("Not enough secure information provided")); } else { onAttributeRequest(data); } // I_AM_HERE // HERE move the settings read from ApplicationController to here // here is when the DeviceController is moved to its thread // and now can start mounting the encrypted partition // and then read the settings. // TODO don't forget to check for the required configurations files and parameters in the settings class. // and take care of the security flag in the Storage class. } /*! * \brief DeviceController::findPendingLogs * \details this function counts downs for the _pendingInterval * when the _pendingCounter reaches 0 will search for the files * and if there is any will get the recent file in the list */ void DeviceController::findPendingLogs() { if ( ! gLogUpload ) return; // no log upload pending detection static QString pendingLog = ""; if( _pendingCounter ) { _pendingCounter -- ; return; } else { _pendingCounter = _pendingInterval; // every minute } QFileInfoList pendingFiles; QString logLoc = Log_Folder_Base; QString logExt = QString("*.%1*").arg(_Logger.logFileNamePendingSubExt()); // "*.u.*"; for( auto logFolder : { Log_Folder_Application, Log_Folder_Service/*, Log_Folder_CloudSync*/ } ) { pendingFiles = Storage::FileHandler::find( logLoc + logFolder, { logExt } ); // look into the list. // if there are pending files, // send a request only for the top on the list /// Note I thought it makes sense to send the oldest on the application and service logs /// but there are some conversation about the situation if something happens on the device, /// and it would be a critical situation to get the recent/top log and won't wait for the old ones to upload. // * When gets uploaded, moves from pending then next one comes to top // the process repeats until there is no file in pending bool uploadOldestFirst = true; //TODO: make if configurable(cfg, or cli) if ( pendingFiles.count() ) { // the most recent/first log file, to first ask for the current log which has just been saved as pending if ( uploadOldestFirst ) { _pendingLog = pendingFiles.last().absoluteFilePath(); } else { _pendingLog = pendingFiles.first().absoluteFilePath(); } QString message = pendingLog; LOG_DEBUG(message); emit didPendingLog( _pendingLog, FileHandler::sha256sum( _pendingLog ) ); // when a pending file found in the first log folder stop // until there is none in first one (log) // then continue to the next log folder (service) ... goto lOut; } } lOut: return; } ///////////////////////////////////////////// DeviceFactoryReset void DeviceController::onAttributeRequest(const DeviceFactoryResetRequestData &vData) { _deviceFactoryResetRequest._data = vData; // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Factory_Reset), _deviceFactoryResetResponse, script) ) return; // ----- check if the process is not running if ( _processFactoryReset.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceFactoryResetResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processFactoryReset, script, timeout_ms); timedProcess->start(); MDeviceFactoryResetResponse model; model._data.mAccepted = false; // will indirectly set the property factoryResetEnabled model._data.mMessage = tr("Factory Reset started."); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::onProcessFactoryResetExitCode * \param vExitCode - the script exit code. * \param vStatus - the script echoed message. */ void DeviceController::onProcessFactoryResetExitCode(int vExitCode, QProcess::ExitStatus vStatus) { // The Exit code in this script is not used. // any other checking is done by UI Software at the moment this script is called. // The only thing matters is the paired device info in text and it will be empty string if error happens. MDeviceFactoryResetResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processFactoryReset.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); // DEBUG: qDebug() << model._data.mMessage << deviceInfo; emit didAttributeResponse (model.data()); emit didFactoryReset (model._data.mAccepted); LOG_APPED_UI(model.data().mMessage); } ///////////////////////////////////////////// DeviceDecommission void DeviceController::onAttributeRequest(const DeviceDecommissionRequestData &vData) { _deviceDecommissionRequest._data = vData; // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, Device_Decommission), _deviceDecommissionResponse, script) ) return; // ----- check if the process is not running if ( _processDecommission.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceDecommissionResponse); return; } // ----- run the process int timeout_ms = 10000; TimedProcess *timedProcess = new TimedProcess(&_processDecommission, script, timeout_ms, { CloudSyncPath }); _processDecommission.setEnvironment(QProcess::systemEnvironment() << QString("PASSWORD=%1").arg(_deviceDecommissionRequest._data.mPassword)); timedProcess->start(); MDeviceDecommissionResponse model; model._data.mAccepted = false; // will indirectly set the property decommissionEnabled model._data.mMessage = tr("Decommissioning started."); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::onProcessDecommissionExitCode * \param vExitCode * \param vStatus */ void DeviceController::onProcessDecommissionExitCode(int vExitCode, QProcess::ExitStatus vStatus) { // The Exit code in this script is not used. // any other checking is done by UI Software at the moment this script is called. // The only thing matters is the paired device info in text and it will be empty string if error happens. MDeviceDecommissionResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processDecommission.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); // DEBUG: qDebug() << model._data.mMessage << deviceInfo; emit didAttributeResponse (model.data()); emit didDecommissioning (model._data.mAccepted); LOG_APPED_UI(model.data().mMessage); } ///////////////////////////////////////////// DeviceUSBMounting void DeviceController::onAttributeRequest(const DeviceUSBMountRequestData &vData) { Q_UNUSED(vData) usbMountReq(vData.usbDevice, vData.isMountRequest); } /*! * \brief DeviceController::usbMountReq * \details Calls the Usb unmount/mount script * \param vIsMount - indicate if the request is for mounting or unmounting * \param vDevice - the path to the USB device */ void DeviceController::usbMountReq(const QString &vDevice, bool vIsMount) { qDebug() << __FUNCTION__ << vDevice << vIsMount; _deviceUSBMountRequest._data.isMountRequest = vIsMount ; _deviceUSBMountRequest._data.usbDevice = vDevice ; // not necessary, but to be consistent // ----- check that script exists. QString script; if ( checkError( DeviceError::checkScript(script, (vIsMount ? USB_Mount : USB_Unmount )), _deviceUSBMountResponse, script) ) return; // ----- check if the process is not running if ( _processUSBMount.state() != QProcess::NotRunning ) { checkError(DeviceError::eDevice_Scripts_Error_IsRunning, _deviceUSBMountResponse); return; } // ----- run the process int timeout_ms = 5000; TimedProcess *timedProcess = new TimedProcess(&_processUSBMount, script, timeout_ms, {vDevice, USB_Mount_Point, USB_File_System}); timedProcess->start(); MDeviceUSBMountResponse model; model._data.mAccepted = false; model._data.mMessage = vIsMount ? tr("USB unmount started.") : tr("USB mount started"); emit didAttributeResponse(model.data()); } /*! * \brief DeviceController::onProcessUSBMountExitCode * \param vExitCode * \param vStatus */ void DeviceController::onProcessUSBMountExitCode(int vExitCode, QProcess::ExitStatus vStatus) { MDeviceUSBMountResponse model; QByteArray deviceInfo; if ( vStatus ) vExitCode = Device::DeviceError::eDevice_Scripts_Error_Status; else deviceInfo = _processUSBMount.readAll(); model.fromByteArray( deviceInfo, &vExitCode ); emit didAttributeResponse(model.data()); // Re-evaluate the USB space available - need to call this here to avoid // visual lag caused by waiting to call this function on the timer timeout usbSpaceCheck(); bool ok = ! vStatus; QString usbDevice = _deviceUSBMountRequest._data.usbDevice; if(_deviceUSBMountRequest._data.isMountRequest) { // *** USB Mount if ( ok && ! _mounted ) { _mounted = true; _removed = false; LOG_DEBUG(QString("USB flash drive %1 has been mounted on %2").arg(usbDevice).arg(USB_Mount_Point)); emit didUSBDriveMount(); } else { usbError(usbDevice); } } else { // *** USB Unmount if ( ok && _mounted ) { _mounted = false; // _umounted = true; // I think it might be needed, but needs more testing. LOG_DEBUG(QString("USB drive %2 unmounted").arg(usbDevice)); emit didUSBDriveUmount(); } else { // the error is irrelevant, commented out for now // usbError(usbDrive); } } // log error and exit return; }