storage_cli.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <cli/cli.h>
  4. #include <lib/toolbox/args.h>
  5. #include <lib/toolbox/md5.h>
  6. #include <lib/toolbox/dir_walk.h>
  7. #include <storage/storage.h>
  8. #include <storage/storage_sd_api.h>
  9. #include <power/power_service/power.h>
  10. #define MAX_NAME_LENGTH 255
  11. static void storage_cli_print_usage() {
  12. printf("Usage:\r\n");
  13. printf("storage <cmd> <path> <args>\r\n");
  14. printf("The path must start with /int or /ext\r\n");
  15. printf("Cmd list:\r\n");
  16. printf("\tinfo\t - get FS info\r\n");
  17. printf("\tformat\t - format filesystem\r\n");
  18. printf("\tlist\t - list files and dirs\r\n");
  19. printf("\ttree\t - list files and dirs, recursive\r\n");
  20. printf("\tremove\t - delete the file or directory\r\n");
  21. printf("\tread\t - read text from file and print file size and content to cli\r\n");
  22. printf(
  23. "\tread_chunks\t - read data from file and print file size and content to cli, <args> should contain how many bytes you want to read in block\r\n");
  24. printf("\twrite\t - read text from cli and append it to file, stops by ctrl+c\r\n");
  25. printf(
  26. "\twrite_chunk\t - read data from cli and append it to file, <args> should contain how many bytes you want to write\r\n");
  27. printf("\tcopy\t - copy file to new file, <args> must contain new path\r\n");
  28. printf("\trename\t - move file to new file, <args> must contain new path\r\n");
  29. printf("\tmkdir\t - creates a new directory\r\n");
  30. printf("\tmd5\t - md5 hash of the file\r\n");
  31. printf("\tstat\t - info about file or dir\r\n");
  32. printf("\ttimestamp\t - last modification timestamp\r\n");
  33. };
  34. static void storage_cli_print_error(FS_Error error) {
  35. printf("Storage error: %s\r\n", storage_error_get_desc(error));
  36. }
  37. static void storage_cli_info(Cli* cli, FuriString* path) {
  38. UNUSED(cli);
  39. Storage* api = furi_record_open(RECORD_STORAGE);
  40. if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
  41. uint64_t total_space;
  42. uint64_t free_space;
  43. FS_Error error =
  44. storage_common_fs_info(api, STORAGE_INT_PATH_PREFIX, &total_space, &free_space);
  45. if(error != FSE_OK) {
  46. storage_cli_print_error(error);
  47. } else {
  48. printf(
  49. "Label: %s\r\nType: LittleFS\r\n%luKiB total\r\n%luKiB free\r\n",
  50. furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Unknown",
  51. (uint32_t)(total_space / 1024),
  52. (uint32_t)(free_space / 1024));
  53. }
  54. } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
  55. SDInfo sd_info;
  56. FS_Error error = storage_sd_info(api, &sd_info);
  57. if(error != FSE_OK) {
  58. storage_cli_print_error(error);
  59. } else {
  60. printf(
  61. "Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n",
  62. sd_info.label,
  63. sd_api_get_fs_type_text(sd_info.fs_type),
  64. sd_info.kb_total,
  65. sd_info.kb_free);
  66. }
  67. } else {
  68. storage_cli_print_usage();
  69. }
  70. furi_record_close(RECORD_STORAGE);
  71. };
  72. static void storage_cli_format(Cli* cli, FuriString* path) {
  73. if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
  74. storage_cli_print_error(FSE_NOT_IMPLEMENTED);
  75. } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
  76. printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n");
  77. char answer = cli_getc(cli);
  78. if(answer == 'y' || answer == 'Y') {
  79. Storage* api = furi_record_open(RECORD_STORAGE);
  80. printf("Formatting, please wait...\r\n");
  81. FS_Error error = storage_sd_format(api);
  82. if(error != FSE_OK) {
  83. storage_cli_print_error(error);
  84. } else {
  85. printf("SD card was successfully formatted.\r\n");
  86. }
  87. furi_record_close(RECORD_STORAGE);
  88. } else {
  89. printf("Cancelled.\r\n");
  90. }
  91. } else {
  92. storage_cli_print_usage();
  93. }
  94. };
  95. static void storage_cli_list(Cli* cli, FuriString* path) {
  96. UNUSED(cli);
  97. if(furi_string_cmp_str(path, "/") == 0) {
  98. printf("\t[D] int\r\n");
  99. printf("\t[D] ext\r\n");
  100. printf("\t[D] any\r\n");
  101. } else {
  102. Storage* api = furi_record_open(RECORD_STORAGE);
  103. File* file = storage_file_alloc(api);
  104. if(storage_dir_open(file, furi_string_get_cstr(path))) {
  105. FileInfo fileinfo;
  106. char name[MAX_NAME_LENGTH];
  107. bool read_done = false;
  108. while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) {
  109. read_done = true;
  110. if(fileinfo.flags & FSF_DIRECTORY) {
  111. printf("\t[D] %s\r\n", name);
  112. } else {
  113. printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size));
  114. }
  115. }
  116. if(!read_done) {
  117. printf("\tEmpty\r\n");
  118. }
  119. } else {
  120. storage_cli_print_error(storage_file_get_error(file));
  121. }
  122. storage_dir_close(file);
  123. storage_file_free(file);
  124. furi_record_close(RECORD_STORAGE);
  125. }
  126. }
  127. static void storage_cli_tree(Cli* cli, FuriString* path) {
  128. if(furi_string_cmp_str(path, "/") == 0) {
  129. furi_string_set(path, STORAGE_INT_PATH_PREFIX);
  130. storage_cli_tree(cli, path);
  131. furi_string_set(path, STORAGE_EXT_PATH_PREFIX);
  132. storage_cli_tree(cli, path);
  133. } else {
  134. Storage* api = furi_record_open(RECORD_STORAGE);
  135. DirWalk* dir_walk = dir_walk_alloc(api);
  136. FuriString* name;
  137. name = furi_string_alloc();
  138. if(dir_walk_open(dir_walk, furi_string_get_cstr(path))) {
  139. FileInfo fileinfo;
  140. bool read_done = false;
  141. while(dir_walk_read(dir_walk, name, &fileinfo) == DirWalkOK) {
  142. read_done = true;
  143. if(fileinfo.flags & FSF_DIRECTORY) {
  144. printf("\t[D] %s\r\n", furi_string_get_cstr(name));
  145. } else {
  146. printf(
  147. "\t[F] %s %lub\r\n",
  148. furi_string_get_cstr(name),
  149. (uint32_t)(fileinfo.size));
  150. }
  151. }
  152. if(!read_done) {
  153. printf("\tEmpty\r\n");
  154. }
  155. } else {
  156. storage_cli_print_error(dir_walk_get_error(dir_walk));
  157. }
  158. furi_string_free(name);
  159. dir_walk_free(dir_walk);
  160. furi_record_close(RECORD_STORAGE);
  161. }
  162. }
  163. static void storage_cli_read(Cli* cli, FuriString* path) {
  164. UNUSED(cli);
  165. Storage* api = furi_record_open(RECORD_STORAGE);
  166. File* file = storage_file_alloc(api);
  167. if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  168. const uint16_t buffer_size = 128;
  169. uint16_t read_size = 0;
  170. uint8_t* data = malloc(buffer_size);
  171. printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
  172. do {
  173. read_size = storage_file_read(file, data, buffer_size);
  174. for(uint16_t i = 0; i < read_size; i++) {
  175. printf("%c", data[i]);
  176. }
  177. } while(read_size > 0);
  178. printf("\r\n");
  179. free(data);
  180. } else {
  181. storage_cli_print_error(storage_file_get_error(file));
  182. }
  183. storage_file_close(file);
  184. storage_file_free(file);
  185. furi_record_close(RECORD_STORAGE);
  186. }
  187. static void storage_cli_write(Cli* cli, FuriString* path) {
  188. Storage* api = furi_record_open(RECORD_STORAGE);
  189. File* file = storage_file_alloc(api);
  190. const uint16_t buffer_size = 512;
  191. uint8_t* buffer = malloc(buffer_size);
  192. if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
  193. printf("Just write your text data. New line by Ctrl+Enter, exit by Ctrl+C.\r\n");
  194. uint32_t read_index = 0;
  195. while(true) {
  196. uint8_t symbol = cli_getc(cli);
  197. if(symbol == CliSymbolAsciiETX) {
  198. uint16_t write_size = read_index % buffer_size;
  199. if(write_size > 0) {
  200. uint16_t written_size = storage_file_write(file, buffer, write_size);
  201. if(written_size != write_size) {
  202. storage_cli_print_error(storage_file_get_error(file));
  203. }
  204. break;
  205. }
  206. }
  207. buffer[read_index % buffer_size] = symbol;
  208. printf("%c", buffer[read_index % buffer_size]);
  209. fflush(stdout);
  210. read_index++;
  211. if(((read_index % buffer_size) == 0)) {
  212. uint16_t written_size = storage_file_write(file, buffer, buffer_size);
  213. if(written_size != buffer_size) {
  214. storage_cli_print_error(storage_file_get_error(file));
  215. break;
  216. }
  217. }
  218. }
  219. printf("\r\n");
  220. } else {
  221. storage_cli_print_error(storage_file_get_error(file));
  222. }
  223. storage_file_close(file);
  224. free(buffer);
  225. storage_file_free(file);
  226. furi_record_close(RECORD_STORAGE);
  227. }
  228. static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) {
  229. Storage* api = furi_record_open(RECORD_STORAGE);
  230. File* file = storage_file_alloc(api);
  231. uint32_t buffer_size;
  232. int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size);
  233. if(parsed_count != 1) {
  234. storage_cli_print_usage();
  235. } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  236. uint64_t file_size = storage_file_size(file);
  237. printf("Size: %lu\r\n", (uint32_t)file_size);
  238. if(buffer_size) {
  239. uint8_t* data = malloc(buffer_size);
  240. while(file_size > 0) {
  241. printf("\r\nReady?\r\n");
  242. cli_getc(cli);
  243. uint16_t read_size = storage_file_read(file, data, buffer_size);
  244. for(uint16_t i = 0; i < read_size; i++) {
  245. putchar(data[i]);
  246. }
  247. file_size -= read_size;
  248. }
  249. free(data);
  250. }
  251. printf("\r\n");
  252. } else {
  253. storage_cli_print_error(storage_file_get_error(file));
  254. }
  255. storage_file_close(file);
  256. storage_file_free(file);
  257. furi_record_close(RECORD_STORAGE);
  258. }
  259. static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) {
  260. Storage* api = furi_record_open(RECORD_STORAGE);
  261. File* file = storage_file_alloc(api);
  262. uint32_t buffer_size;
  263. int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size);
  264. if(parsed_count != 1) {
  265. storage_cli_print_usage();
  266. } else {
  267. if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
  268. printf("Ready\r\n");
  269. if(buffer_size) {
  270. uint8_t* buffer = malloc(buffer_size);
  271. for(uint32_t i = 0; i < buffer_size; i++) {
  272. buffer[i] = cli_getc(cli);
  273. }
  274. uint16_t written_size = storage_file_write(file, buffer, buffer_size);
  275. if(written_size != buffer_size) {
  276. storage_cli_print_error(storage_file_get_error(file));
  277. }
  278. free(buffer);
  279. }
  280. } else {
  281. storage_cli_print_error(storage_file_get_error(file));
  282. }
  283. storage_file_close(file);
  284. }
  285. storage_file_free(file);
  286. furi_record_close(RECORD_STORAGE);
  287. }
  288. static void storage_cli_stat(Cli* cli, FuriString* path) {
  289. UNUSED(cli);
  290. Storage* api = furi_record_open(RECORD_STORAGE);
  291. if(furi_string_cmp_str(path, "/") == 0) {
  292. printf("Storage\r\n");
  293. } else if(
  294. furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0 ||
  295. furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0 ||
  296. furi_string_cmp_str(path, STORAGE_ANY_PATH_PREFIX) == 0) {
  297. uint64_t total_space;
  298. uint64_t free_space;
  299. FS_Error error =
  300. storage_common_fs_info(api, furi_string_get_cstr(path), &total_space, &free_space);
  301. if(error != FSE_OK) {
  302. storage_cli_print_error(error);
  303. } else {
  304. printf(
  305. "Storage, %luKiB total, %luKiB free\r\n",
  306. (uint32_t)(total_space / 1024),
  307. (uint32_t)(free_space / 1024));
  308. }
  309. } else {
  310. FileInfo fileinfo;
  311. FS_Error error = storage_common_stat(api, furi_string_get_cstr(path), &fileinfo);
  312. if(error == FSE_OK) {
  313. if(fileinfo.flags & FSF_DIRECTORY) {
  314. printf("Directory\r\n");
  315. } else {
  316. printf("File, size: %lub\r\n", (uint32_t)(fileinfo.size));
  317. }
  318. } else {
  319. storage_cli_print_error(error);
  320. }
  321. }
  322. furi_record_close(RECORD_STORAGE);
  323. }
  324. static void storage_cli_timestamp(Cli* cli, FuriString* path) {
  325. UNUSED(cli);
  326. Storage* api = furi_record_open(RECORD_STORAGE);
  327. uint32_t timestamp = 0;
  328. FS_Error error = storage_common_timestamp(api, furi_string_get_cstr(path), &timestamp);
  329. if(error != FSE_OK) {
  330. printf("Invalid arguments\r\n");
  331. } else {
  332. printf("Timestamp %lu\r\n", timestamp);
  333. }
  334. furi_record_close(RECORD_STORAGE);
  335. }
  336. static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) {
  337. UNUSED(cli);
  338. Storage* api = furi_record_open(RECORD_STORAGE);
  339. FuriString* new_path;
  340. new_path = furi_string_alloc();
  341. if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
  342. storage_cli_print_usage();
  343. } else {
  344. FS_Error error = storage_common_copy(
  345. api, furi_string_get_cstr(old_path), furi_string_get_cstr(new_path));
  346. if(error != FSE_OK) {
  347. storage_cli_print_error(error);
  348. }
  349. }
  350. furi_string_free(new_path);
  351. furi_record_close(RECORD_STORAGE);
  352. }
  353. static void storage_cli_remove(Cli* cli, FuriString* path) {
  354. UNUSED(cli);
  355. Storage* api = furi_record_open(RECORD_STORAGE);
  356. FS_Error error = storage_common_remove(api, furi_string_get_cstr(path));
  357. if(error != FSE_OK) {
  358. storage_cli_print_error(error);
  359. }
  360. furi_record_close(RECORD_STORAGE);
  361. }
  362. static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) {
  363. UNUSED(cli);
  364. Storage* api = furi_record_open(RECORD_STORAGE);
  365. FuriString* new_path;
  366. new_path = furi_string_alloc();
  367. if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
  368. storage_cli_print_usage();
  369. } else {
  370. FS_Error error = storage_common_rename(
  371. api, furi_string_get_cstr(old_path), furi_string_get_cstr(new_path));
  372. if(error != FSE_OK) {
  373. storage_cli_print_error(error);
  374. }
  375. }
  376. furi_string_free(new_path);
  377. furi_record_close(RECORD_STORAGE);
  378. }
  379. static void storage_cli_mkdir(Cli* cli, FuriString* path) {
  380. UNUSED(cli);
  381. Storage* api = furi_record_open(RECORD_STORAGE);
  382. FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path));
  383. if(error != FSE_OK) {
  384. storage_cli_print_error(error);
  385. }
  386. furi_record_close(RECORD_STORAGE);
  387. }
  388. static void storage_cli_md5(Cli* cli, FuriString* path) {
  389. UNUSED(cli);
  390. Storage* api = furi_record_open(RECORD_STORAGE);
  391. File* file = storage_file_alloc(api);
  392. if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
  393. const uint16_t buffer_size = 512;
  394. const uint8_t hash_size = 16;
  395. uint8_t* data = malloc(buffer_size);
  396. uint8_t* hash = malloc(sizeof(uint8_t) * hash_size);
  397. md5_context* md5_ctx = malloc(sizeof(md5_context));
  398. md5_starts(md5_ctx);
  399. while(true) {
  400. uint16_t read_size = storage_file_read(file, data, buffer_size);
  401. if(read_size == 0) break;
  402. md5_update(md5_ctx, data, read_size);
  403. }
  404. md5_finish(md5_ctx, hash);
  405. free(md5_ctx);
  406. for(uint8_t i = 0; i < hash_size; i++) {
  407. printf("%02x", hash[i]);
  408. }
  409. printf("\r\n");
  410. free(hash);
  411. free(data);
  412. } else {
  413. storage_cli_print_error(storage_file_get_error(file));
  414. }
  415. storage_file_close(file);
  416. storage_file_free(file);
  417. furi_record_close(RECORD_STORAGE);
  418. }
  419. void storage_cli(Cli* cli, FuriString* args, void* context) {
  420. UNUSED(context);
  421. FuriString* cmd;
  422. FuriString* path;
  423. cmd = furi_string_alloc();
  424. path = furi_string_alloc();
  425. do {
  426. if(!args_read_string_and_trim(args, cmd)) {
  427. storage_cli_print_usage();
  428. break;
  429. }
  430. if(!args_read_probably_quoted_string_and_trim(args, path)) {
  431. storage_cli_print_usage();
  432. break;
  433. }
  434. if(furi_string_cmp_str(cmd, "info") == 0) {
  435. storage_cli_info(cli, path);
  436. break;
  437. }
  438. if(furi_string_cmp_str(cmd, "format") == 0) {
  439. storage_cli_format(cli, path);
  440. break;
  441. }
  442. if(furi_string_cmp_str(cmd, "list") == 0) {
  443. storage_cli_list(cli, path);
  444. break;
  445. }
  446. if(furi_string_cmp_str(cmd, "tree") == 0) {
  447. storage_cli_tree(cli, path);
  448. break;
  449. }
  450. if(furi_string_cmp_str(cmd, "read") == 0) {
  451. storage_cli_read(cli, path);
  452. break;
  453. }
  454. if(furi_string_cmp_str(cmd, "read_chunks") == 0) {
  455. storage_cli_read_chunks(cli, path, args);
  456. break;
  457. }
  458. if(furi_string_cmp_str(cmd, "write") == 0) {
  459. storage_cli_write(cli, path);
  460. break;
  461. }
  462. if(furi_string_cmp_str(cmd, "write_chunk") == 0) {
  463. storage_cli_write_chunk(cli, path, args);
  464. break;
  465. }
  466. if(furi_string_cmp_str(cmd, "copy") == 0) {
  467. storage_cli_copy(cli, path, args);
  468. break;
  469. }
  470. if(furi_string_cmp_str(cmd, "remove") == 0) {
  471. storage_cli_remove(cli, path);
  472. break;
  473. }
  474. if(furi_string_cmp_str(cmd, "rename") == 0) {
  475. storage_cli_rename(cli, path, args);
  476. break;
  477. }
  478. if(furi_string_cmp_str(cmd, "mkdir") == 0) {
  479. storage_cli_mkdir(cli, path);
  480. break;
  481. }
  482. if(furi_string_cmp_str(cmd, "md5") == 0) {
  483. storage_cli_md5(cli, path);
  484. break;
  485. }
  486. if(furi_string_cmp_str(cmd, "stat") == 0) {
  487. storage_cli_stat(cli, path);
  488. break;
  489. }
  490. if(furi_string_cmp_str(cmd, "timestamp") == 0) {
  491. storage_cli_timestamp(cli, path);
  492. break;
  493. }
  494. storage_cli_print_usage();
  495. } while(false);
  496. furi_string_free(path);
  497. furi_string_free(cmd);
  498. }
  499. static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) {
  500. UNUSED(args);
  501. UNUSED(context);
  502. printf("All data will be lost! Are you sure (y/n)?\r\n");
  503. char c = cli_getc(cli);
  504. if(c == 'y' || c == 'Y') {
  505. printf("Data will be wiped after reboot.\r\n");
  506. furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset);
  507. power_reboot(PowerBootModeNormal);
  508. } else {
  509. printf("Safe choice.\r\n");
  510. }
  511. }
  512. void storage_on_system_start() {
  513. #ifdef SRV_CLI
  514. Cli* cli = furi_record_open(RECORD_CLI);
  515. cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL);
  516. cli_add_command(
  517. cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL);
  518. furi_record_close(RECORD_CLI);
  519. #else
  520. UNUSED(storage_cli_factory_reset);
  521. #endif
  522. }