libuav original
Dependents: UAVCAN UAVCAN_Subscriber
firmware_version_checker.hpp
00001 /**************************************************************************** 00002 * 00003 * Copyright (c) 2015 PX4 Development Team. All rights reserved. 00004 * Author: Pavel Kirienko <pavel.kirienko@gmail.com> 00005 * David Sidrane <david_s5@usa.net> 00006 * 00007 ****************************************************************************/ 00008 00009 #ifndef UAVCAN_POSIX_FIRMWARE_VERSION_CHECKER_HPP_INCLUDED 00010 #define UAVCAN_POSIX_FIRMWARE_VERSION_CHECKER_HPP_INCLUDED 00011 00012 #include <cstdint> 00013 #include <cstdio> 00014 #include <cstring> 00015 #include <fcntl.h> 00016 #include <cerrno> 00017 #include <dirent.h> 00018 00019 #include <uavcan/protocol/firmware_update_trigger.hpp> 00020 00021 // TODO Get rid of the macro 00022 #if !defined(DIRENT_ISFILE) && defined(DT_REG) 00023 # define DIRENT_ISFILE(dtype) ((dtype) == DT_REG) 00024 #endif 00025 00026 namespace uavcan_posix 00027 { 00028 /** 00029 * Firmware version checking logic. 00030 * Refer to @ref FirmwareUpdateTrigger for details. 00031 */ 00032 class FirmwareVersionChecker : public uavcan::IFirmwareVersionChecker 00033 { 00034 enum { FilePermissions = 438 }; ///< 0o666 00035 00036 enum { MaxBasePathLength = 128 }; 00037 00038 /** 00039 * This type is used for the base path 00040 */ 00041 typedef uavcan::MakeString<MaxBasePathLength>::Type BasePathString; 00042 00043 /** 00044 * Maximum length of full path including / the file name 00045 */ 00046 enum { MaxPathLength = uavcan::protocol::file::Path::FieldTypes::path::MaxSize + MaxBasePathLength }; 00047 00048 /** 00049 * This type is used internally for the full path to file 00050 */ 00051 typedef uavcan::MakeString<MaxPathLength>::Type PathString; 00052 00053 BasePathString base_path_; 00054 BasePathString cache_path_; 00055 00056 /** 00057 * The folder where the files will be copied and read from 00058 */ 00059 static const char* getCacheDir() { return "c"; } 00060 00061 static void addSlash(BasePathString& path) 00062 { 00063 if (path.back() != getPathSeparator()) 00064 { 00065 path.push_back(getPathSeparator()); 00066 } 00067 } 00068 00069 static void removeSlash(BasePathString& path) 00070 { 00071 if (path.back() == getPathSeparator()) 00072 { 00073 path.pop_back(); 00074 } 00075 } 00076 00077 void setFirmwareBasePath(const char* path) 00078 { 00079 base_path_ = path; 00080 } 00081 00082 void setFirmwareCachePath(const char* path) 00083 { 00084 cache_path_ = path; 00085 } 00086 00087 int copyIfNot(const char* srcpath, const char* destpath) 00088 { 00089 using namespace std; 00090 00091 // Does the file exist 00092 int rv = 0; 00093 int dfd = open(destpath, O_RDONLY, 0); 00094 00095 if (dfd >= 0) 00096 { 00097 // Close it and exit 0 00098 (void)close(dfd); 00099 } 00100 else 00101 { 00102 uint8_t buffer[512]; 00103 00104 dfd = open(destpath, O_WRONLY | O_CREAT, FilePermissions); 00105 if (dfd < 0) 00106 { 00107 rv = -errno; 00108 } 00109 else 00110 { 00111 int sfd = open(srcpath, O_RDONLY, 0); 00112 if (sfd < 0) 00113 { 00114 rv = -errno; 00115 } 00116 else 00117 { 00118 ssize_t size = 0; 00119 do 00120 { 00121 size = ::read(sfd, buffer, sizeof(buffer)); 00122 if (size != 0) 00123 { 00124 if (size < 0) 00125 { 00126 rv = -errno; 00127 } 00128 else 00129 { 00130 rv = 0; 00131 ssize_t remaining = size; 00132 ssize_t total_written = 0; 00133 ssize_t written = 0; 00134 do 00135 { 00136 written = write(dfd, &buffer[total_written], remaining); 00137 if (written < 0) 00138 { 00139 rv = -errno; 00140 } 00141 else 00142 { 00143 total_written += written; 00144 remaining -= written; 00145 } 00146 } 00147 while (written > 0 && remaining > 0); 00148 } 00149 } 00150 } 00151 while (rv == 0 && size != 0); 00152 00153 (void)close(sfd); 00154 } 00155 (void)close(dfd); 00156 } 00157 } 00158 return rv; 00159 } 00160 00161 struct AppDescriptor 00162 { 00163 uavcan::uint8_t signature[sizeof(uavcan::uint64_t)]; 00164 uavcan::uint64_t image_crc; 00165 uavcan::uint32_t image_size; 00166 uavcan::uint32_t vcs_commit; 00167 uavcan::uint8_t major_version; 00168 uavcan::uint8_t minor_version; 00169 uavcan::uint8_t reserved[6]; 00170 }; 00171 00172 static int getFileInfo(const char* path, AppDescriptor& descriptor) 00173 { 00174 using namespace std; 00175 00176 const unsigned MaxChunk = 512 / sizeof(uint64_t); 00177 00178 uint64_t signature = 0; 00179 std::memcpy(&signature, "APDesc00", 8); 00180 00181 int rv = -ENOENT; 00182 uint64_t chunk[MaxChunk]; 00183 int fd = open(path, O_RDONLY); 00184 00185 if (fd >= 0) 00186 { 00187 AppDescriptor* pdescriptor = UAVCAN_NULLPTR; 00188 00189 while (pdescriptor == UAVCAN_NULLPTR) 00190 { 00191 int len = read(fd, chunk, sizeof(chunk)); 00192 00193 if (len == 0) 00194 { 00195 break; 00196 } 00197 00198 if (len < 0) 00199 { 00200 rv = -errno; 00201 goto out_close; 00202 } 00203 00204 uint64_t* p = &chunk[0]; 00205 00206 do 00207 { 00208 if (*p == signature) 00209 { 00210 pdescriptor = reinterpret_cast<AppDescriptor*>(p); // FIXME TODO This breaks strict aliasing 00211 descriptor = *pdescriptor; 00212 rv = 0; 00213 break; 00214 } 00215 } 00216 while (p++ <= &chunk[MaxChunk - (sizeof(AppDescriptor) / sizeof(chunk[0]))]); 00217 } 00218 00219 out_close: 00220 (void)close(fd); 00221 } 00222 return rv; 00223 } 00224 00225 protected: 00226 /** 00227 * This method will be invoked when the class obtains a response to GetNodeInfo request. 00228 * 00229 * @param node_id Node ID that this GetNodeInfo response was received from. 00230 * 00231 * @param node_info Actual node info structure; refer to uavcan.protocol.GetNodeInfo for details. 00232 * 00233 * @param out_firmware_file_path The implementation should return the firmware image path via this argument. 00234 * Note that this path must be reachable via uavcan.protocol.file.Read service. 00235 * Refer to @ref FileServer and @ref BasicFileServer for details. 00236 * 00237 * @return True - the class will begin sending update requests. 00238 * False - the node will be ignored, no request will be sent. 00239 */ 00240 virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID, 00241 const uavcan::protocol::GetNodeInfo::Response& node_info, 00242 FirmwareFilePath& out_firmware_file_path) 00243 { 00244 using namespace std; 00245 00246 /* This is a work around for two issues. 00247 * 1) FirmwareFilePath is 40 00248 * 2) OK using is using 32 for max file names. 00249 * 00250 * So for the file: 00251 * org.pixhawk.px4cannode-v1-0.1.59efc137.uavcan.bin 00252 * +---fw 00253 * +-c <----------- Files are cashed here. 00254 * +--- px4cannode-v1.59efc137.bin <------ A Firmware file 00255 * +---org.pixhawk.px4cannode-v1 <---------- node_info.name 00256 * +---1.0 <-------------------------------- node_info.name's hardware_version.major,minor 00257 * + - px4cannode-v1.59efc137.bin <---- A well known file must match the name 00258 * in the root fw folder, so if it does not exist 00259 * it is copied up MUST BE < 32 Characters 00260 */ 00261 bool rv = false; 00262 00263 char fname_root[MaxBasePathLength + 1]; 00264 int n = snprintf(fname_root, sizeof(fname_root), "%s%s/%d.%d", 00265 getFirmwareBasePath().c_str(), 00266 node_info.name.c_str(), 00267 node_info.hardware_version.major, 00268 node_info.hardware_version.minor); 00269 00270 if (n > 0 && n < (int)sizeof(fname_root) - 2) 00271 { 00272 DIR* const fwdir = opendir(fname_root); 00273 00274 fname_root[n++] = getPathSeparator(); 00275 fname_root[n++] = '\0'; 00276 00277 if (fwdir != UAVCAN_NULLPTR) 00278 { 00279 struct dirent* pfile = UAVCAN_NULLPTR; 00280 while ((pfile = readdir(fwdir)) != UAVCAN_NULLPTR) 00281 { 00282 if (DIRENT_ISFILE(pfile->d_type)) 00283 { 00284 // Open any bin file in there. 00285 if (strstr(pfile->d_name, ".bin") != UAVCAN_NULLPTR) 00286 { 00287 PathString full_src_path = fname_root; 00288 full_src_path += pfile->d_name; 00289 00290 PathString full_dst_path = getFirmwareCachePath().c_str(); 00291 full_dst_path += pfile->d_name; 00292 00293 // ease the burden on the user 00294 int cr = copyIfNot(full_src_path.c_str(), full_dst_path.c_str()); 00295 00296 // We have a file, is it a valid image 00297 AppDescriptor descriptor; 00298 00299 std::memset(&descriptor, 0, sizeof(descriptor)); 00300 00301 if (cr == 0 && getFileInfo(full_dst_path.c_str(), descriptor) == 0) 00302 { 00303 volatile AppDescriptor descriptorC = descriptor; 00304 descriptorC.reserved[1]++; 00305 00306 if (node_info.software_version.image_crc == 0 || 00307 (node_info.software_version.major == 0 && node_info.software_version.minor == 0) || 00308 descriptor.image_crc != node_info.software_version.image_crc) 00309 { 00310 rv = true; 00311 out_firmware_file_path = pfile->d_name; 00312 } 00313 break; 00314 } 00315 } 00316 } 00317 } 00318 (void)closedir(fwdir); 00319 } 00320 } 00321 return rv; 00322 } 00323 00324 /** 00325 * This method will be invoked when a node responds to the update request with an error. If the request simply 00326 * times out, this method will not be invoked. 00327 * Note that if by the time of arrival of the response the node is already removed, this method will not be called. 00328 * 00329 * SPECIAL CASE: If the node responds with ERROR_IN_PROGRESS, the class will assume that further requesting 00330 * is not needed anymore. This method will not be invoked. 00331 * 00332 * @param node_id Node ID that returned this error. 00333 * 00334 * @param error_response Contents of the error response. It contains error code and text. 00335 * 00336 * @param out_firmware_file_path New firmware path if a retry is needed. Note that this argument will be 00337 * initialized with old path, so if the same path needs to be reused, this 00338 * argument should be left unchanged. 00339 * 00340 * @return True - the class will continue sending update requests with new firmware path. 00341 * False - the node will be forgotten, new requests will not be sent. 00342 */ 00343 virtual bool shouldRetryFirmwareUpdate(uavcan::NodeID, 00344 const uavcan::protocol::file::BeginFirmwareUpdate::Response&, 00345 FirmwareFilePath&) 00346 { 00347 // TODO: Limit the number of attempts per node 00348 return true; 00349 } 00350 00351 public: 00352 const BasePathString& getFirmwareBasePath() const { return base_path_; } 00353 00354 const BasePathString& getFirmwareCachePath() const { return cache_path_; } 00355 00356 static char getPathSeparator() 00357 { 00358 return static_cast<char>(uavcan::protocol::file::Path::SEPARATOR); 00359 } 00360 00361 /** 00362 * Creates the Directories were the files will be stored 00363 * 00364 * This is directory structure is in support of a workaround 00365 * for the issues that FirmwareFilePath is 40 00366 * 00367 * It creates a path structure: 00368 * +---(base_path) 00369 * +-c <----------- Files are cached here. 00370 */ 00371 int createFwPaths(const char* base_path) 00372 { 00373 using namespace std; 00374 int rv = -uavcan::ErrInvalidParam; 00375 00376 if (base_path) 00377 { 00378 const int len = strlen(base_path); 00379 00380 if (len > 0 && len < base_path_.MaxSize) 00381 { 00382 setFirmwareBasePath(base_path); 00383 removeSlash(base_path_); 00384 const char* path = getFirmwareBasePath().c_str(); 00385 00386 setFirmwareCachePath(path); 00387 addSlash(cache_path_); 00388 cache_path_ += getCacheDir(); 00389 00390 rv = 0; 00391 struct stat sb; 00392 if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) 00393 { 00394 rv = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO); 00395 } 00396 00397 path = getFirmwareCachePath().c_str(); 00398 00399 if (rv == 0 && (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode))) 00400 { 00401 rv = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO); 00402 } 00403 00404 addSlash(base_path_); 00405 addSlash(cache_path_); 00406 00407 if (rv >= 0) 00408 { 00409 if ((getFirmwareCachePath().size() + uavcan::protocol::file::Path::FieldTypes::path::MaxSize) > 00410 MaxPathLength) 00411 { 00412 rv = -uavcan::ErrInvalidConfiguration; 00413 } 00414 } 00415 } 00416 } 00417 return rv; 00418 } 00419 00420 const char* getFirmwarePath() const 00421 { 00422 return getFirmwareCachePath().c_str(); 00423 } 00424 }; 00425 } 00426 00427 #endif // Include guard
Generated on Tue Jul 12 2022 17:17:31 by 1.7.2