From fa11a76143159f9934f69b877e0a5e17ee6a9990 Mon Sep 17 00:00:00 2001 From: David Sidrane Date: Wed, 20 May 2015 18:17:16 -1000 Subject: [PATCH] Refactoring paths --- .../protocol/firmware_update_trigger.hpp | 1 + .../basic_file_server_backend.hpp | 147 ++++++++++ .../uavcan_posix/file_server_backend.hpp | 183 ------------ .../include/uavcan_posix/firmware_common.hpp | 217 +++++++++++++++ .../uavcan_posix/firmware_version_checker.hpp | 262 ++++++++++++++++++ 5 files changed, 627 insertions(+), 183 deletions(-) create mode 100644 libuavcan_drivers/posix/include/uavcan_posix/basic_file_server_backend.hpp delete mode 100644 libuavcan_drivers/posix/include/uavcan_posix/file_server_backend.hpp create mode 100644 libuavcan_drivers/posix/include/uavcan_posix/firmware_common.hpp create mode 100644 libuavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp diff --git a/libuavcan/include/uavcan/protocol/firmware_update_trigger.hpp b/libuavcan/include/uavcan/protocol/firmware_update_trigger.hpp index f32e1598c7..788536c07c 100644 --- a/libuavcan/include/uavcan/protocol/firmware_update_trigger.hpp +++ b/libuavcan/include/uavcan/protocol/firmware_update_trigger.hpp @@ -468,6 +468,7 @@ public: UAVCAN_ASSERT((ret > 0) ? isTimerRunning() : true); return ret; } + }; } diff --git a/libuavcan_drivers/posix/include/uavcan_posix/basic_file_server_backend.hpp b/libuavcan_drivers/posix/include/uavcan_posix/basic_file_server_backend.hpp new file mode 100644 index 0000000000..c28e0cd381 --- /dev/null +++ b/libuavcan_drivers/posix/include/uavcan_posix/basic_file_server_backend.hpp @@ -0,0 +1,147 @@ +/**************************************************************************** +* +* Copyright (c) 2015 PX4 Development Team. All rights reserved. +* Author: Pavel Kirienko +* David Sidrane +* +****************************************************************************/ + +#ifndef UAVCAN_POSIX_BASIC_FILE_SERVER_BACKEND_HPP_INCLUDED +#define UAVCAN_POSIX_BASIC_FILE_SERVER_BACKEND_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "firmware_common.hpp" + +namespace uavcan_posix +{ +/** + * This interface implements a POSIX compliant IFileServerBackend interface + */ +class BasicFileSeverBackend : public uavcan::IFileServerBackend +{ + + enum { FilePermissions = 438 }; ///< 0o666 + + FirmwareCommon::BasePathString base_path; + +public: + /** + * + * Back-end for uavcan.protocol.file.GetInfo. + * Implementation of this method is required. + * On success the method must return zero. + */ + __attribute__((optimize("O0"))) + virtual int16_t getInfo(const Path& path, uint64_t& out_crc64, uint32_t& out_size, EntryType& out_type) + { + + enum { MaxBufferLength = 256 }; + + int rv = uavcan::protocol::file::Error::INVALID_VALUE; + + FirmwareCommon::PathString fw_full_path = FirmwareCommon::getFirmwareCachePath(FirmwareCommon::PathString( + base_path.c_str())); + + if (path.size() > 0 && (fw_full_path.size() + path.size()) <= FirmwareCommon::PathString::MaxSize) + { + + fw_full_path += path; + FirmwareCommon fw; + fw.getFileInfo(fw_full_path); + out_crc64 = fw.descriptor.image_crc; + out_size = fw.descriptor.image_size; + // todo Using fixed flag FLAG_READABLE until we add file permission checks to return actual value. + out_type.flags = uavcan::protocol::file::EntryType::FLAG_FILE | + uavcan::protocol::file::EntryType::FLAG_READABLE;; + rv = 0; + } + return rv; + } + + /** + * Back-end for uavcan.protocol.file.Read. + * Implementation of this method is required. + * @ref inout_size is set to @ref ReadSize; read operation is required to return exactly this amount, except + * if the end of file is reached. + * On success the method must return zero. + */ + __attribute__((optimize("O0"))) + virtual int16_t read(const Path& path, const uint32_t offset, uint8_t* out_buffer, uint16_t& inout_size) + { + + int rv = uavcan::protocol::file::Error::INVALID_VALUE; + + FirmwareCommon::PathString fw_full_path = FirmwareCommon::getFirmwareCachePath(FirmwareCommon::PathString( + base_path.c_str())); + + if (path.size() > 0 && (fw_full_path.size() + path.size()) <= FirmwareCommon::PathString::MaxSize) + { + + fw_full_path += path; + + int fd = open(fw_full_path.c_str(), O_RDONLY); + + if (fd < 0) + { + rv = errno; + } + else + { + + if (::lseek(fd, offset, SEEK_SET) < 0) + { + rv = errno; + } + else + { + //todo uses a read at offset to fill on EAGAIN + ssize_t len = ::read(fd, out_buffer, inout_size); + + if (len < 0) + { + rv = errno; + } + else + { + + inout_size = len; + rv = 0; + } + } + close(fd); + } + } + return rv; + } + + /** + * Initializes the file server based back-end storage by passing a path to + * the directory where files will be stored. + */ + __attribute__((optimize("O0"))) + int init(const FirmwareCommon::BasePathString& path) + { + using namespace std; + base_path = path; + int rv = FirmwareCommon::create_fw_paths(base_path); + return rv; + } +}; + +} + +#endif // Include guard diff --git a/libuavcan_drivers/posix/include/uavcan_posix/file_server_backend.hpp b/libuavcan_drivers/posix/include/uavcan_posix/file_server_backend.hpp deleted file mode 100644 index d6280e6bc3..0000000000 --- a/libuavcan_drivers/posix/include/uavcan_posix/file_server_backend.hpp +++ /dev/null @@ -1,183 +0,0 @@ -/**************************************************************************** -* -* Copyright (c) 2015 PX4 Development Team. All rights reserved. -* Author: Pavel Kirienko -* David Sidrane -* -****************************************************************************/ - -#ifndef UAVCAN_POSIX_FILE_SERVER_BACKEND_HPP_INCLUDED -#define UAVCAN_POSIX_FILE_SERVER_BACKEND_HPP_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace uavcan_posix -{ -/** - * This interface implements a POSIX compliant IFileServerBackend interface - */ -class FileSeverBackend : public uavcan::IFileServerBackend -{ - /** - * Maximum length of full path including / the file name - */ - enum { MaxPathLength = Path::MaxSize + 128 }; - - enum { FilePermissions = 438 }; ///< 0o666 - - /** - * This type is used for the path - */ - typedef uavcan::MakeString::Type PathString; - - PathString base_path; - -public: - /** - * - * Backend for uavcan.protocol.file.GetInfo. - * Implementation of this method is required. - * On success the method must return zero. - */ - virtual int16_t getInfo(const Path& path, uint64_t& out_crc64, uint32_t& out_size, EntryType& out_type) - { - - enum { MaxBufferLength = 256 }; - - int rv = -uavcan::ErrInvalidParam; - if (path.size()) { - - rv = -uavcan::ErrFailure; - - PathString root_path = base_path.c_str(); - root_path += path; - - uint32_t size = 0; - int fd = open(path.c_str(), O_RDONLY); - if (fd >= 0) - { - uavcan::DataTypeSignatureCRC crc; - ssize_t len; - - uint8_t bytes[MaxBufferLength]; - - do { - - len = ::read(fd, bytes, MaxBufferLength); - - if (len < 0) { - goto out_close; - } - - if (len > 0) { - size += len; - crc.add(bytes, len); - } - - } while(len); - out_crc64 = crc.get(); - out_size = size; - EntryType t; - t.flags = uavcan::protocol::file::EntryType::FLAG_FILE; - out_type = t; - rv = 0; - } - -out_close: - close(fd); - } - return rv; - } - - /** - * Backend for uavcan.protocol.file.Read. - * Implementation of this method is required. - * @ref inout_size is set to @ref ReadSize; read operation is required to return exactly this amount, except - * if the end of file is reached. - * On success the method must return zero. - */ - virtual int16_t read(const Path& path, const uint32_t offset, uint8_t* out_buffer, uint16_t& inout_size) - { - - int rv = -uavcan::ErrInvalidParam; - - if (path.size()) { - - rv = -uavcan::ErrFailure; - - PathString root_path = base_path.c_str(); - root_path += path; - - int fd = open(path.c_str(), O_RDONLY); - - if (fd >= 0) - { - if (::lseek(fd, offset, SEEK_SET) >= 0) { - - ssize_t len = ::read(fd, out_buffer, inout_size); - close(fd); - - if (len < 0) { - return rv; - } - inout_size = len; - rv = 0; - } - } - } - return rv; - } - - /** - * Initializes the file serrver based backend storage by passing a path to - * the directory where files will be stored. - */ - int init(const PathString& path) - { - using namespace std; - - int rv = -uavcan::ErrInvalidParam; - - if (path.size() > 0) - { - base_path = path.c_str(); - - if (base_path.back() == '/') - { - base_path.pop_back(); - } - - rv = 0; - struct stat sb; - if (stat(base_path.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) - { - rv = mkdir(base_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); - } - if (rv >= 0 ) - { - base_path.push_back('/'); - if ((base_path.size() + Path::MaxSize) > MaxPathLength) - { - rv = -uavcan::ErrInvalidConfiguration; - } - } - } - return rv; - } -}; - -} - -#endif // Include guard diff --git a/libuavcan_drivers/posix/include/uavcan_posix/firmware_common.hpp b/libuavcan_drivers/posix/include/uavcan_posix/firmware_common.hpp new file mode 100644 index 0000000000..6973b45f6c --- /dev/null +++ b/libuavcan_drivers/posix/include/uavcan_posix/firmware_common.hpp @@ -0,0 +1,217 @@ +/**************************************************************************** +* +* Copyright (c) 2015 PX4 Development Team. All rights reserved. +* Author: Pavel Kirienko +* David Sidrane +* +****************************************************************************/ + +#ifndef UAVCAN_POSIX_FIRMWARE_COMMON_HPP_INCLUDED +#define UAVCAN_POSIX_FIRMWARE_COMMON_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include + +namespace uavcan_posix +{ +/** + * Firmware version checking logic. + * Refer to @ref FirmwareUpdateTrigger for details. + */ +class FirmwareCommon +{ + /* The folder where the files will be copied and Read from */ + + static const char* get_cache_dir_() + { + return "c"; + } + +public: + + enum { MaxBasePathLength = 128 }; + + /** + * This type is used for the base path + */ + typedef uavcan::MakeString::Type BasePathString; + + /** + * Maximum length of full path including / the file name + */ + enum { MaxPathLength = uavcan::protocol::file::Path::FieldTypes::path::MaxSize + MaxBasePathLength }; + + /** + * This type is used internally for the full path to file + */ + typedef uavcan::MakeString::Type PathString; + + static char getPathSeparator() { return static_cast(uavcan::protocol::file::Path::SEPARATOR); } + + typedef struct app_descriptor_t + { + uint8_t signature[sizeof(uint64_t)]; + uint64_t image_crc; + uint32_t image_size; + uint32_t vcs_commit; + uint8_t major_version; + uint8_t minor_version; + uint8_t reserved[6]; + } app_descriptor_t; + + app_descriptor_t descriptor; + + /** + * This method Is used to get the app_descriptor_t from a firmware image + * + * @param path Complete path to the file + * + * @param add_separator Complete path with trailing separator + * + * + * @return 0 if the app_descriptor_t was found and out_descriptor + * has been updated. + * Otherwise -errno is returned + * -ENOENT Signature was not found. + */ + + static PathString getFirmwareCachePath(const PathString& p, bool add_separator = true) + { + PathString r; + r = p; + r.push_back(getPathSeparator()); + r += get_cache_dir_(); + if (add_separator) { + r.push_back(getPathSeparator()); + } + return r; + } + + + static BasePathString getFirmwareCachePath(const BasePathString& p, bool add_separator = true) + { + BasePathString r; + r = p; + r.push_back(getPathSeparator()); + r += get_cache_dir_(); + if (add_separator) { + r.push_back(getPathSeparator()); + } + return r; + } + + int getFileInfo(PathString& path) + { + enum { MaxChunk = (512 / sizeof(uint64_t)) }; + int rv = -ENOENT; + uint64_t chunk[MaxChunk]; + int fd = open(path.c_str(), O_RDONLY); + + if (fd >= 0) + { + app_descriptor_t *pdescriptor = 0; + + while(!pdescriptor) + { + int len = read(fd, chunk, sizeof(chunk)); + + if (len == 0) + { + break; + } + + if (len < 0) + { + rv = -errno; + goto out_close; + } + + uint64_t *p = &chunk[0]; + + do + { + if (*p == sig.ull) + { + pdescriptor = (app_descriptor_t *)p; + descriptor = *pdescriptor; + rv = 0; + break; + } + } + while(p++ <= &chunk[MaxChunk - (sizeof(app_descriptor_t) / sizeof(chunk[0]))]); + } + out_close: + close(fd); + } + return rv; + } + + + /** + * Creates the Directories were the files will be stored + */ + + static int create_fw_paths(FirmwareCommon::BasePathString& path) + { + using namespace std; + + int rv = -uavcan::ErrInvalidParam; + + if (path.size() > 0) + { + if (path.back() == FirmwareCommon::getPathSeparator()) + { + path.pop_back(); + } + + rv = 0; + struct stat sb; + if (stat(path.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) + { + rv = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); + } + + PathString cache = getFirmwareCachePath(PathString(path.c_str()), false); + if (rv >= 0 && (stat(cache.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode))) + { + rv = mkdir(cache.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); + } + + if (rv >= 0 ) + { + path.push_back(FirmwareCommon::getPathSeparator()); + if ((path.size() + uavcan::protocol::file::Path::FieldTypes::path::MaxSize) > + FirmwareCommon::MaxPathLength) + { + rv = -uavcan::ErrInvalidConfiguration; + } + } + } + return rv; + } + + +private: + +#define APP_DESCRIPTOR_SIGNATURE_ID 'A', 'P', 'D', 'e', 's', 'c' +#define APP_DESCRIPTOR_SIGNATURE_REV '0', '0' +#define APP_DESCRIPTOR_SIGNATURE APP_DESCRIPTOR_SIGNATURE_ID, APP_DESCRIPTOR_SIGNATURE_REV + + union + { + uint64_t ull; + char text[sizeof(uint64_t)]; + } sig = { + .text = {APP_DESCRIPTOR_SIGNATURE} + }; + +}; +} + +#endif // Include guard diff --git a/libuavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp b/libuavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp new file mode 100644 index 0000000000..69c71c3a39 --- /dev/null +++ b/libuavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp @@ -0,0 +1,262 @@ +/**************************************************************************** +* +* Copyright (c) 2015 PX4 Development Team. All rights reserved. +* Author: Pavel Kirienko +* David Sidrane +* +****************************************************************************/ + +#ifndef UAVCAN_POSIX_FIRMWARE_VERSION_CHECKER_HPP_INCLUDED +#define UAVCAN_POSIX_FIRMWARE_VERSION_CHECKER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "firmware_common.hpp" + +namespace uavcan_posix +{ +/** + * Firmware version checking logic. + * Refer to @ref FirmwareUpdateTrigger for details. + */ +class FirmwareVersionChecker : public uavcan::IFirmwareVersionChecker +{ + enum { FilePermissions = 438 }; ///< 0o666 + + FirmwareCommon::BasePathString base_path; + FirmwareCommon::BasePathString cache_path; + +public: + + /** + * This method will be invoked when the class obtains a response to GetNodeInfo request. + * + * @param node_id Node ID that this GetNodeInfo response was received from. + * + * @param node_info Actual node info structure; refer to uavcan.protocol.GetNodeInfo for details. + * + * @param out_firmware_file_path The implementation should return the firmware image path via this argument. + * Note that this path must be reachable via uavcan.protocol.file.Read service. + * Refer to @ref FileServer and @ref BasicFileServer for details. + * + * @return True - the class will begin sending update requests. + * False - the node will be ignored, no request will be sent. + */ + __attribute__((optimize("O0"))) + virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID node_id, const + uavcan::protocol::GetNodeInfo::Response& node_info, + FirmwareFilePath& out_firmware_file_path) + { + /* This is a work around for two issues. + * 1) FirmwareFilePath is 40 + * 2) Nuttx is using 32 for max file names. + * + * So for the file: + * org.pixhawk.px4cannode-v1-0.1.59efc137.uavcan.bin + * +---fw + * +-c <----------- Files are cashed here. + * +--- 59efc137.bin <---------- A unknown Firmware file + * +---org.pixhawk.px4cannode-v1 <---------- node_info.name + * +---1.0 <-------------------------------- node_info.name's hardware_version.major,minor + * + - 59efc137.bin <----------- A well known file must match the name + * in the root fw folder, so if it does not exist + * it is copied up + */ + + bool rv = false; + + char fname_root[FirmwareCommon::MaxBasePathLength + 1]; + int n = snprintf(fname_root, sizeof(fname_root), "%s%s/%d.%d", + base_path.c_str(), + node_info.name.c_str(), + node_info.hardware_version.major, + node_info.hardware_version.minor); + + if (n > 0) + { + FAR DIR *fwdir = opendir(fname_root); + + if (fwdir) + { + FAR struct dirent *pfile; + while((pfile = readdir(fwdir)) != NULL) + { + if (DIRENT_ISFILE(pfile->d_type)) + { + // Open any bin file in there. + if (strstr(pfile->d_name, ".bin")) + { + FirmwareCommon::PathString full_src_path = fname_root; + full_src_path.push_back(FirmwareCommon::getPathSeparator()); + full_src_path += pfile->d_name; + + FirmwareCommon::PathString full_dst_path = cache_path.c_str(); + full_dst_path += pfile->d_name; + + /* ease the burden on the user */ + int cr = copy_if_not(full_src_path.c_str(), full_dst_path.c_str()); + + /* We have a file, is it a valid image */ + + FirmwareCommon fw; + + if (cr == 0 && fw.getFileInfo(full_dst_path) == 0) + { + if ((node_info.software_version.major == 0 && + node_info.software_version.minor == 0) || + fw.descriptor.image_crc != + node_info.software_version.image_crc) + { + rv = true; + out_firmware_file_path = pfile->d_name; + } + break; + } + } + } + } + closedir(fwdir); + } + } + return rv; + } + + /** + * This method will be invoked when a node responds to the update request with an error. If the request simply + * times out, this method will not be invoked. + * Note that if by the time of arrival of the response the node is already removed, this method will not be called. + * + * SPECIAL CASE: If the node responds with ERROR_IN_PROGRESS, the class will assume that further requesting + * is not needed anymore. This method will not be invoked. + * + * @param node_id Node ID that returned this error. + * + * @param error_response Contents of the error response. It contains error code and text. + * + * @param out_firmware_file_path New firmware path if a retry is needed. Note that this argument will be + * initialized with old path, so if the same path needs to be reused, this + * argument should be left unchanged. + * + * @return True - the class will continue sending update requests with new firmware path. + * False - the node will be forgotten, new requests will not be sent. + */ + __attribute__((optimize("O0"))) + virtual bool shouldRetryFirmwareUpdate(uavcan::NodeID node_id, + const uavcan::protocol::file::BeginFirmwareUpdate::Response& error_response, + FirmwareFilePath& out_firmware_file_path) + { + return true; + + } + + /** + * This node is invoked when the node responds to the update request with confirmation. + * Note that if by the time of arrival of the response the node is already removed, this method will not be called. + * + * Implementation is optional; default one does nothing. + * + * @param node_id Node ID that confirmed the request. + * + * @param response Actual response. + */ + virtual void handleFirmwareUpdateConfirmation(uavcan::NodeID node_id, + const uavcan::protocol::file::BeginFirmwareUpdate::Response& response) + { + (void)node_id; + (void)response; + } + + FirmwareVersionChecker() { } + + virtual ~FirmwareVersionChecker() { } + + /** + * Initializes the file server based back-end storage by passing a path to + * the directory where files will be stored. + */ + __attribute__((optimize("O0"))) + int init(const char * path) + { + base_path = path; + cache_path = FirmwareCommon::getFirmwareCachePath(base_path); + if (base_path.back() != FirmwareCommon::getPathSeparator()) + { + base_path.push_back(FirmwareCommon::getPathSeparator()); + } + return 0; + } + +private: + + + __attribute__((optimize("O0"))) + int copy_if_not(const char *srcpath, const char * destpath) + { + /* Does the file exits */ + int rv = 0; + int dfd = open(destpath, O_RDONLY, 0); + + if (dfd >= 0) + { + /* Close it and exit 0 */ + close(dfd); + } + else + { + uint8_t buffer[512]; + + dfd = open(destpath, O_WRONLY | O_CREAT , FilePermissions); + if (dfd < 0) + { + rv = -errno; + } + else + { + int sfd = open(srcpath, O_RDONLY, 0); + if (sfd < 0) + { + rv = -errno; + } + else + { + ssize_t size; + do + { + size = ::read(sfd, buffer, sizeof(buffer)); + if (size != 0) + { + if (size < 0) + { + + rv = -errno; + + } + else + { + + if (size != write(dfd, buffer, size)) + { + rv = -errno; + } + } + } + } + while (rv == 0 && size); + close(sfd); + } + close(dfd); + } + } + return rv; + } +}; +} + +#endif // Include guard