Various fixes around Companion trainer mode (#7116)
[opentx.git] / radio / src / targets / simu / simufatfs.cpp
blob250ab93f28350b22526c8f957de53ab5799a8c26
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <map>
22 #include <string>
23 #include <vector>
24 #include <algorithm>
25 #include "opentx.h"
27 #if defined(SIMU_USE_SDCARD) // rest of file is excluded otherwise
29 #if defined(_MSC_VER) || !defined (__GNUC__)
30 #define MSVC_BUILD 1
31 #else
32 #define MSVC_BUILD 0
33 #endif
35 // NOTE: the #include order is important here, sensitive on different platoforms.
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <sys/stat.h>
42 #if MSVC_BUILD
43 #include <direct.h>
44 #include <stdlib.h>
45 #include <sys/utime.h>
46 #define mkdir(s, f) _mkdir(s)
47 #else
48 #include <sys/time.h>
49 #include <utime.h>
50 #endif
52 #include "ff.h"
54 namespace simu {
55 #include <dirent.h>
56 #if !defined(_MSC_VER)
57 #include <libgen.h>
58 #endif
61 std::string simuSdDirectory; // path to the root of the SD card image
62 std::string simuSettingsDirectory; // path to the root of the models and settings (only for the radios that use SD for model storage)
64 bool isPathDelimiter(char delimiter)
66 return delimiter == '/';
69 std::string removeTrailingPathDelimiter(const std::string & path)
71 std::string result = path;
72 while (!result.empty() && isPathDelimiter(result.back())) {
73 result.pop_back();
75 return result;
78 std::string fixPathDelimiters(const char * path)
80 // replace all '\' characters with '/'
81 std::string result(path);
82 std::replace(result.begin(), result.end(), '\\', '/');
83 // TRACE_SIMPGMSPACE("fixPathDelimiters(): %s -> %s", path, result.c_str());
84 return result;
87 void simuFatfsSetPaths(const char * sdPath, const char * settingsPath)
89 if (sdPath) {
90 simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(sdPath));
92 else {
93 char buff[1024];
94 f_getcwd(buff, sizeof(buff)-1);
95 simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(buff));
97 if (settingsPath) {
98 simuSettingsDirectory = removeTrailingPathDelimiter(fixPathDelimiters(settingsPath));
100 TRACE_SIMPGMSPACE("simuFatfsSetPaths(): simuSdDirectory: \"%s\"", simuSdDirectory.c_str());
101 TRACE_SIMPGMSPACE("simuFatfsSetPaths(): simuSettingsDirectory: \"%s\"", simuSettingsDirectory.c_str());
104 bool startsWith(const std::string & str, const std::string & prefix)
106 return str.length() >= prefix.length() &&
107 str.compare(0, prefix.length(), prefix) == 0;
110 bool endsWith(const std::string & str, const std::string & suffix)
112 return str.length() >= suffix.length() &&
113 str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;
116 bool redirectToSettingsDirectory(const std::string & path)
119 Decide if we use special simuSettingsDirectory path or normal path
121 We use special path for:
122 * radio settings and models list in /RADIO directory
123 * model (*.bin) files in /MODELS directory
125 if (!simuSettingsDirectory.empty()) {
126 #if defined(COLORLCD)
127 if (path == RADIO_MODELSLIST_PATH || path == RADIO_SETTINGS_PATH) {
128 return true;
130 #endif
131 if (startsWith(path, "/MODELS") && endsWith(path, MODELS_EXT)) {
132 return true;
135 return false;
138 std::string convertToSimuPath(const char * path)
140 std::string result;
141 if (isPathDelimiter(path[0])) {
142 if (redirectToSettingsDirectory(path)) {
143 // TRACE("REDIRECT ********************************************");
144 result = simuSettingsDirectory + std::string(path);
146 else {
147 result = simuSdDirectory + std::string(path);
150 else {
151 result = std::string(path);
153 TRACE_SIMPGMSPACE("convertToSimuPath(): %s -> %s", path, result.c_str());
154 return result;
157 std::string convertFromSimuPath(const char * path)
159 std::string result;
160 if (startsWith(path, simuSdDirectory)) {
161 result = std::string(path).substr(simuSdDirectory.length(), std::string::npos);
162 if (result.empty()) {
163 result = "/";
166 else {
167 result = std::string(path);
168 if (!result.empty() && !isPathDelimiter(result[0])) {
169 result = "/" + result;
172 TRACE_SIMPGMSPACE("convertFromSimuPath(): %s -> %s", path, result.c_str());
173 return result;
176 typedef std::map<std::string, std::string> filemap_t;
178 filemap_t fileMap;
180 void splitPath(const std::string & path, std::string & dir, std::string & name)
182 #if MSVC_BUILD
183 char drive[_MAX_DRIVE];
184 char directory[_MAX_DIR];
185 char fname[_MAX_FNAME];
186 char ext[_MAX_EXT];
187 _splitpath(path.c_str(), drive, directory, fname, ext);
188 name = std::string(fname) + std::string(ext);
189 dir = std::string(drive) + std::string(directory);
190 #else
191 char * buff = new char[path.length()+1];
192 strcpy(buff, path.c_str());
193 name = simu::basename(buff);
194 strcpy(buff, path.c_str());
195 dir = simu::dirname(buff);
196 delete[] buff;
197 #endif
201 #if !MSVC_BUILD
202 bool isFile(const std::string & fullName, unsigned int d_type)
204 #if defined(WIN32) || defined(__APPLE__) || defined(__FreeBSD__)
205 #define REGULAR_FILE DT_REG
206 #define SYMBOLIC_LINK DT_LNK
207 #else
208 #define REGULAR_FILE simu::DT_REG
209 #define SYMBOLIC_LINK simu::DT_LNK
210 #endif
212 if (d_type == REGULAR_FILE) return true;
213 if (d_type == SYMBOLIC_LINK) {
214 struct stat tmp;
215 if (stat(fullName.c_str(), &tmp) == 0) {
216 // TRACE_SIMPGMSPACE("\tsymlik: %s is %s", fullName.c_str(), (tmp.st_mode & S_IFREG) ? "file" : "other");
217 if (tmp.st_mode & S_IFREG) return true;
220 return false;
222 #endif
224 std::vector<std::string> listDirectoryFiles(const std::string & dirName)
226 std::vector<std::string> result;
228 #if MSVC_BUILD
229 std::string searchName = dirName + "*";
230 // TRACE_SIMPGMSPACE("\tsearching for: %s", fileName.c_str());
231 WIN32_FIND_DATA ffd;
232 HANDLE hFind = FindFirstFile(searchName.c_str(), &ffd);
233 if (INVALID_HANDLE_VALUE != hFind) {
234 do {
235 if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
236 std::string fullName = dirName + std::string(ffd.cFileName);
237 // TRACE_SIMPGMSPACE("listDirectoryFiles(): %s", fullName.c_str());
238 result.push_back(fullName);
241 while (FindNextFile(hFind, &ffd) != 0);
243 #else
244 simu::DIR * dir = simu::opendir(dirName.c_str());
245 if (dir) {
246 struct simu::dirent * res;
247 while ((res = simu::readdir(dir)) != 0) {
248 std::string fullName = dirName + "/" + std::string(res->d_name);
249 if (isFile(fullName, res->d_type)) {
250 // TRACE_SIMPGMSPACE("listDirectoryFiles(): %s", fullName.c_str());
251 result.push_back(fullName);
254 simu::closedir(dir);
256 #endif
257 return result;
260 std::string findTrueFileName(const std::string & path)
262 TRACE_SIMPGMSPACE("findTrueFileName(%s)", path.c_str());
263 std::string result;
264 filemap_t::iterator i = fileMap.find(path);
265 if (i != fileMap.end()) {
266 result = i->second;
267 TRACE_SIMPGMSPACE("\tfound in map: %s", result.c_str());
268 return result;
270 else {
271 //find file and add to map
272 std::string dirName;
273 std::string fileName;
274 splitPath(path, dirName, fileName);
275 std::vector<std::string> files = listDirectoryFiles(dirName);
276 for(unsigned int i=0; i<files.size(); ++i) {
277 if (!strcasecmp(files[i].c_str(), path.c_str())) {
278 TRACE_SIMPGMSPACE("\tfound: %s", files[i].c_str());
279 fileMap.insert(filemap_t::value_type(path, files[i]));
280 return files[i];
284 TRACE_SIMPGMSPACE("\tnot found");
285 return std::string(path);
288 FRESULT f_stat (const TCHAR * name, FILINFO *fno)
290 std::string path = convertToSimuPath(name);
291 std::string realPath = findTrueFileName(path);
292 struct stat tmp;
293 if (stat(realPath.c_str(), &tmp)) {
294 TRACE_SIMPGMSPACE("f_stat(%s) = error %d (%s)", path.c_str(), errno, strerror(errno));
295 return FR_INVALID_NAME;
297 else {
298 TRACE_SIMPGMSPACE("f_stat(%s) = OK", path.c_str());
299 if (fno) {
300 fno->fattrib = (tmp.st_mode & S_IFDIR) ? AM_DIR : 0;
301 // convert to FatFs fdate/ftime
302 struct tm *ltime = localtime(&tmp.st_mtime);
303 fno->fdate = ((ltime->tm_year - 80) << 9) | ((ltime->tm_mon + 1) << 5) | ltime->tm_mday;
304 fno->ftime = (ltime->tm_hour << 11) | (ltime->tm_min << 5) | (ltime->tm_sec / 2);
305 fno->fsize = (DWORD)tmp.st_size;
307 return FR_OK;
311 FRESULT f_mount (FATFS* ,const TCHAR*, BYTE opt)
313 return FR_OK;
316 FRESULT f_open (FIL * fil, const TCHAR *name, BYTE flag)
318 std::string path = convertToSimuPath(name);
319 std::string realPath = findTrueFileName(path);
320 fil->obj.fs = 0;
321 if (!(flag & FA_WRITE)) {
322 struct stat tmp;
323 if (stat(realPath.c_str(), &tmp)) {
324 TRACE_SIMPGMSPACE("f_open(%s) = INVALID_NAME (FIL %p)", path.c_str(), fil);
325 return FR_INVALID_NAME;
327 fil->obj.objsize = tmp.st_size;
328 fil->fptr = 0;
330 fil->obj.fs = (FATFS*)fopen(realPath.c_str(), (flag & FA_WRITE) ? ((flag & FA_CREATE_ALWAYS) ? "wb+" : "ab+") : "rb+");
331 fil->fptr = 0;
332 if (fil->obj.fs) {
333 TRACE_SIMPGMSPACE("f_open(%s, %x) = %p (FIL %p)", path.c_str(), flag, fil->obj.fs, fil);
334 return FR_OK;
336 TRACE_SIMPGMSPACE("f_open(%s) = error %d (%s) (FIL %p)", path.c_str(), errno, strerror(errno), fil);
337 return FR_INVALID_NAME;
340 FRESULT f_read (FIL* fil, void* data, UINT size, UINT* read)
342 if (fil && fil->obj.fs) {
343 *read = fread(data, 1, size, (FILE*)fil->obj.fs);
344 fil->fptr += *read;
345 // TRACE_SIMPGMSPACE("fread(%p) %u, %u", fil->obj.fs, size, *read);
347 return FR_OK;
350 FRESULT f_write (FIL* fil, const void* data, UINT size, UINT* written)
352 if (fil && fil->obj.fs) {
353 *written = fwrite(data, 1, size, (FILE*)fil->obj.fs);
354 fil->fptr += size;
355 // TRACE_SIMPGMSPACE("fwrite(%p) %u, %u", fil->obj.fs, size, *written);
357 return FR_OK;
360 TCHAR * f_gets (TCHAR* buff, int len, FIL* fil)
362 if (fil && fil->obj.fs) {
363 buff = fgets(buff, len, (FILE*)fil->obj.fs);
364 if (buff != nullptr) {
365 fil->fptr = *buff;
367 // TRACE_SIMPGMSPACE("fgets(%p) %u, %s", fil->obj.fs, len, buff);
369 return buff;
372 FRESULT f_lseek (FIL* fil, DWORD offset)
374 if (fil && fil->obj.fs) {
375 fseek((FILE*)fil->obj.fs, offset, SEEK_SET);
376 fil->fptr = offset;
378 return FR_OK;
381 UINT f_size(FIL* fil)
383 if (fil && fil->obj.fs) {
384 long curr = ftell((FILE*)fil->obj.fs);
385 fseek((FILE*)fil->obj.fs, 0, SEEK_END);
386 long size = ftell((FILE*)fil->obj.fs);
387 fseek((FILE*)fil->obj.fs, curr, SEEK_SET);
388 TRACE_SIMPGMSPACE("f_size(%p) %u", fil->obj.fs, size);
389 return size;
391 return 0;
394 FRESULT f_close (FIL * fil)
396 TRACE_SIMPGMSPACE("f_close(%p) (FIL:%p)", fil->obj.fs, fil);
397 if (fil->obj.fs) {
398 fclose((FILE*)fil->obj.fs);
399 fil->obj.fs = nullptr;
401 return FR_OK;
404 FRESULT f_chdir (const TCHAR *name)
406 std::string path = convertToSimuPath(name);
407 if (chdir(path.c_str())) {
408 TRACE_SIMPGMSPACE("f_chdir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno));
409 return FR_NO_PATH;
411 TRACE_SIMPGMSPACE("f_chdir(%s)", path.c_str());
412 return FR_OK;
415 FRESULT f_opendir (DIR * rep, const TCHAR * name)
417 std::string path = convertToSimuPath(name);
418 rep->obj.fs = (FATFS *)simu::opendir(path.c_str());
419 if (rep->obj.fs) {
420 TRACE_SIMPGMSPACE("f_opendir(%s) = OK", path.c_str());
421 return FR_OK;
423 TRACE_SIMPGMSPACE("f_opendir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno));
424 return FR_NO_PATH;
427 FRESULT f_closedir (DIR * rep)
429 TRACE_SIMPGMSPACE("f_closedir(%p)", rep);
430 if (rep->obj.fs) {
431 simu::closedir((simu::DIR *)rep->obj.fs);
433 return FR_OK;
436 FRESULT f_readdir (DIR * rep, FILINFO * fil)
438 simu::dirent * ent;
439 if (!rep->obj.fs) return FR_NO_FILE;
440 for(;;) {
441 ent = simu::readdir((simu::DIR *)rep->obj.fs);
442 if (!ent) return FR_NO_FILE;
443 if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..") ) break;
446 #if defined(WIN32) || !defined(__GNUC__) || defined(__APPLE__) || defined(__FreeBSD__)
447 fil->fattrib = (ent->d_type == DT_DIR ? AM_DIR : 0);
448 #else
449 if (ent->d_type == simu::DT_UNKNOWN || ent->d_type == simu::DT_LNK) {
450 fil->fattrib = 0;
451 struct stat buf;
452 if (stat(ent->d_name, &buf) == 0) {
453 fil->fattrib = (S_ISDIR(buf.st_mode) ? AM_DIR : 0);
456 else {
457 fil->fattrib = (ent->d_type == simu::DT_DIR ? AM_DIR : 0);
459 #endif
461 memset(fil->fname, 0, _MAX_LFN);
462 strcpy(fil->fname, ent->d_name);
463 // TRACE_SIMPGMSPACE("f_readdir(): %s", fil->fname);
464 return FR_OK;
467 FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len)
469 TRACE_SIMPGMSPACE("Format SD...");
470 return FR_OK;
473 FRESULT f_mkdir (const TCHAR * name)
475 std::string path = convertToSimuPath(name);
476 #if defined(WIN32) && defined(__GNUC__)
477 if (mkdir(path.c_str())) {
478 #else
479 if (mkdir(path.c_str(), 0777)) {
480 #endif
481 TRACE_SIMPGMSPACE("mkdir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno));
482 return FR_INVALID_NAME;
484 else {
485 TRACE_SIMPGMSPACE("mkdir(%s) = OK", path.c_str());
486 return FR_OK;
488 return FR_OK;
491 FRESULT f_unlink (const TCHAR * name)
493 std::string path = convertToSimuPath(name);
494 if (unlink(path.c_str())) {
495 TRACE_SIMPGMSPACE("f_unlink(%s) = error %d (%s)", path.c_str(), errno, strerror(errno));
496 return FR_INVALID_NAME;
498 else {
499 TRACE_SIMPGMSPACE("f_unlink(%s) = OK", path.c_str());
500 return FR_OK;
504 FRESULT f_rename(const TCHAR *oldname, const TCHAR *newname)
506 std::string old = convertToSimuPath(oldname);
507 std::string path = convertToSimuPath(newname);
509 if (rename(old.c_str(), path.c_str()) < 0) {
510 TRACE_SIMPGMSPACE("f_rename(%s, %s) = error %d (%s)", old.c_str(), path.c_str(), errno, strerror(errno));
511 return FR_INVALID_NAME;
513 TRACE_SIMPGMSPACE("f_rename(%s, %s) = OK", old.c_str(), path.c_str());
514 return FR_OK;
517 FRESULT f_utime(const TCHAR* path, const FILINFO* fno)
519 if (fno == nullptr)
520 return FR_INVALID_PARAMETER;
522 std::string simpath = convertToSimuPath(path);
523 std::string realPath = findTrueFileName(simpath);
524 struct utimbuf newTimes;
525 struct tm ltime;
527 // convert from FatFs fdate/ftime
528 ltime.tm_year = ((fno->fdate >> 9) & 0x7F) + 80;
529 ltime.tm_mon = ((fno->fdate >> 5) & 0xF) - 1;
530 ltime.tm_mday = (fno->fdate & 0x1F);
531 ltime.tm_hour = ((fno->ftime >> 11) & 0x1F);
532 ltime.tm_min = ((fno->ftime >> 5) & 0x3F);
533 ltime.tm_sec = (fno->ftime & 0x1F) * 2;
534 ltime.tm_isdst = -1; // force mktime() to check dst
536 newTimes.modtime = mktime(&ltime);
537 newTimes.actime = newTimes.modtime;
539 if (utime(realPath.c_str(), &newTimes)) {
540 TRACE_SIMPGMSPACE("f_utime(%s) = error %d (%s)", simpath.c_str(), errno, strerror(errno));
541 return FR_DENIED;
543 else {
544 TRACE_SIMPGMSPACE("f_utime(%s) set mtime = %s", simpath.c_str(), ctime(&newTimes.modtime));
545 return FR_OK;
549 int f_putc (TCHAR c, FIL * fil)
551 if (fil && fil->obj.fs) fwrite(&c, 1, 1, (FILE*)fil->obj.fs);
552 return FR_OK;
555 int f_puts (const TCHAR * str, FIL * fil)
557 int n;
558 for (n = 0; *str; str++, n++) {
559 if (f_putc(*str, fil) == EOF) return EOF;
561 return n;
564 int f_printf (FIL *fil, const TCHAR * format, ...)
566 va_list arglist;
567 va_start(arglist, format);
568 if (fil && fil->obj.fs) vfprintf((FILE*)fil->obj.fs, format, arglist);
569 va_end(arglist);
570 return 0;
573 FRESULT f_getcwd (TCHAR *path, UINT sz_path)
575 char cwd[1024];
576 if (!getcwd(cwd, 1024)) {
577 TRACE_SIMPGMSPACE("f_getcwd() = getcwd() error %d (%s)", errno, strerror(errno));
578 strcpy(path, ".");
579 return FR_NO_PATH;
582 std::string result = convertFromSimuPath(fixPathDelimiters(cwd).c_str());
583 if (result.length() > sz_path) {
584 //TRACE_SIMPGMSPACE("f_getcwd(): buffer too short");
585 return FR_NOT_ENOUGH_CORE;
588 strcpy(path, result.c_str());
589 TRACE_SIMPGMSPACE("f_getcwd() = %s", path);
590 return FR_OK;
593 FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs)
595 // just fake that we always have some clusters free
596 *nclst = 10;
597 return FR_OK;
600 #if defined(PCBSKY9X)
601 int32_t Card_state = SD_ST_MOUNTED;
602 uint32_t Card_CSD[4]; // TODO elsewhere
603 #endif
605 #endif // #if defined(SIMU_USE_SDCARD)