tar_archive.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #include "tar_archive.h"
  2. #include <microtar.h>
  3. #include <storage/storage.h>
  4. #include <furi.h>
  5. #include <toolbox/path.h>
  6. #define TAG "TarArch"
  7. #define MAX_NAME_LEN 255
  8. #define FILE_BLOCK_SIZE 512
  9. #define FILE_OPEN_NTRIES 10
  10. #define FILE_OPEN_RETRY_DELAY 25
  11. typedef struct TarArchive {
  12. Storage* storage;
  13. mtar_t tar;
  14. tar_unpack_file_cb unpack_cb;
  15. void* unpack_cb_context;
  16. } TarArchive;
  17. /* API WRAPPER */
  18. static int mtar_storage_file_write(void* stream, const void* data, unsigned size) {
  19. uint16_t bytes_written = storage_file_write(stream, data, size);
  20. return (bytes_written == size) ? bytes_written : MTAR_EWRITEFAIL;
  21. }
  22. static int mtar_storage_file_read(void* stream, void* data, unsigned size) {
  23. uint16_t bytes_read = storage_file_read(stream, data, size);
  24. return (bytes_read == size) ? bytes_read : MTAR_EREADFAIL;
  25. }
  26. static int mtar_storage_file_seek(void* stream, unsigned offset) {
  27. bool res = storage_file_seek(stream, offset, true);
  28. return res ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
  29. }
  30. static int mtar_storage_file_close(void* stream) {
  31. if(stream) {
  32. storage_file_close(stream);
  33. storage_file_free(stream);
  34. }
  35. return MTAR_ESUCCESS;
  36. }
  37. const struct mtar_ops filesystem_ops = {
  38. .read = mtar_storage_file_read,
  39. .write = mtar_storage_file_write,
  40. .seek = mtar_storage_file_seek,
  41. .close = mtar_storage_file_close,
  42. };
  43. TarArchive* tar_archive_alloc(Storage* storage) {
  44. furi_check(storage);
  45. TarArchive* archive = malloc(sizeof(TarArchive));
  46. archive->storage = storage;
  47. archive->unpack_cb = NULL;
  48. return archive;
  49. }
  50. bool tar_archive_open(TarArchive* archive, const char* path, TarOpenMode mode) {
  51. furi_assert(archive);
  52. FS_AccessMode access_mode;
  53. FS_OpenMode open_mode;
  54. int mtar_access = 0;
  55. switch(mode) {
  56. case TAR_OPEN_MODE_READ:
  57. mtar_access = MTAR_READ;
  58. access_mode = FSAM_READ;
  59. open_mode = FSOM_OPEN_EXISTING;
  60. break;
  61. case TAR_OPEN_MODE_WRITE:
  62. mtar_access = MTAR_WRITE;
  63. access_mode = FSAM_WRITE;
  64. open_mode = FSOM_CREATE_ALWAYS;
  65. break;
  66. default:
  67. return false;
  68. }
  69. File* stream = storage_file_alloc(archive->storage);
  70. if(!storage_file_open(stream, path, access_mode, open_mode)) {
  71. storage_file_free(stream);
  72. return false;
  73. }
  74. mtar_init(&archive->tar, mtar_access, &filesystem_ops, stream);
  75. return true;
  76. }
  77. void tar_archive_free(TarArchive* archive) {
  78. furi_assert(archive);
  79. if(mtar_is_open(&archive->tar)) {
  80. mtar_close(&archive->tar);
  81. }
  82. free(archive);
  83. }
  84. void tar_archive_set_file_callback(TarArchive* archive, tar_unpack_file_cb callback, void* context) {
  85. furi_assert(archive);
  86. archive->unpack_cb = callback;
  87. archive->unpack_cb_context = context;
  88. }
  89. static int tar_archive_entry_counter(mtar_t* tar, const mtar_header_t* header, void* param) {
  90. UNUSED(tar);
  91. UNUSED(header);
  92. furi_assert(param);
  93. int32_t* counter = param;
  94. (*counter)++;
  95. return 0;
  96. }
  97. int32_t tar_archive_get_entries_count(TarArchive* archive) {
  98. int32_t counter = 0;
  99. if(mtar_foreach(&archive->tar, tar_archive_entry_counter, &counter) != MTAR_ESUCCESS) {
  100. counter = -1;
  101. }
  102. return counter;
  103. }
  104. bool tar_archive_dir_add_element(TarArchive* archive, const char* dirpath) {
  105. furi_assert(archive);
  106. return (mtar_write_dir_header(&archive->tar, dirpath) == MTAR_ESUCCESS);
  107. }
  108. bool tar_archive_finalize(TarArchive* archive) {
  109. furi_assert(archive);
  110. return (mtar_finalize(&archive->tar) == MTAR_ESUCCESS);
  111. }
  112. bool tar_archive_store_data(
  113. TarArchive* archive,
  114. const char* path,
  115. const uint8_t* data,
  116. const int32_t data_len) {
  117. furi_assert(archive);
  118. return (
  119. tar_archive_file_add_header(archive, path, data_len) &&
  120. tar_archive_file_add_data_block(archive, data, data_len) &&
  121. tar_archive_file_finalize(archive));
  122. }
  123. bool tar_archive_file_add_header(TarArchive* archive, const char* path, const int32_t data_len) {
  124. furi_assert(archive);
  125. return (mtar_write_file_header(&archive->tar, path, data_len) == MTAR_ESUCCESS);
  126. }
  127. bool tar_archive_file_add_data_block(
  128. TarArchive* archive,
  129. const uint8_t* data_block,
  130. const int32_t block_len) {
  131. furi_assert(archive);
  132. return (mtar_write_data(&archive->tar, data_block, block_len) == block_len);
  133. }
  134. bool tar_archive_file_finalize(TarArchive* archive) {
  135. furi_assert(archive);
  136. return (mtar_end_data(&archive->tar) == MTAR_ESUCCESS);
  137. }
  138. typedef struct {
  139. TarArchive* archive;
  140. const char* work_dir;
  141. Storage_name_converter converter;
  142. } TarArchiveDirectoryOpParams;
  143. static bool archive_extract_current_file(TarArchive* archive, const char* dst_path) {
  144. mtar_t* tar = &archive->tar;
  145. File* out_file = storage_file_alloc(archive->storage);
  146. uint8_t* readbuf = malloc(FILE_BLOCK_SIZE);
  147. bool success = true;
  148. uint8_t n_tries = FILE_OPEN_NTRIES;
  149. do {
  150. while(n_tries-- > 0) {
  151. if(storage_file_open(out_file, dst_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  152. break;
  153. }
  154. FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", dst_path, n_tries);
  155. storage_file_close(out_file);
  156. furi_delay_ms(FILE_OPEN_RETRY_DELAY);
  157. }
  158. if(!storage_file_is_open(out_file)) {
  159. success = false;
  160. break;
  161. }
  162. while(!mtar_eof_data(tar)) {
  163. int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE);
  164. if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) {
  165. success = false;
  166. break;
  167. }
  168. }
  169. } while(false);
  170. storage_file_free(out_file);
  171. free(readbuf);
  172. return success;
  173. }
  174. static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, void* param) {
  175. UNUSED(tar);
  176. TarArchiveDirectoryOpParams* op_params = param;
  177. TarArchive* archive = op_params->archive;
  178. bool skip_entry = false;
  179. if(archive->unpack_cb) {
  180. skip_entry = !archive->unpack_cb(
  181. header->name, header->type == MTAR_TDIR, archive->unpack_cb_context);
  182. }
  183. if(skip_entry) {
  184. FURI_LOG_W(TAG, "filter: skipping entry \"%s\"", header->name);
  185. return 0;
  186. }
  187. FuriString* full_extracted_fname;
  188. if(header->type == MTAR_TDIR) {
  189. full_extracted_fname = furi_string_alloc();
  190. path_concat(op_params->work_dir, header->name, full_extracted_fname);
  191. bool create_res =
  192. storage_simply_mkdir(archive->storage, furi_string_get_cstr(full_extracted_fname));
  193. furi_string_free(full_extracted_fname);
  194. return create_res ? 0 : -1;
  195. }
  196. if(header->type != MTAR_TREG) {
  197. FURI_LOG_W(TAG, "not extracting unsupported type \"%s\"", header->name);
  198. return 0;
  199. }
  200. FURI_LOG_D(TAG, "Extracting %u bytes to '%s'", header->size, header->name);
  201. FuriString* converted_fname = furi_string_alloc_set(header->name);
  202. if(op_params->converter) {
  203. op_params->converter(converted_fname);
  204. }
  205. full_extracted_fname = furi_string_alloc();
  206. path_concat(op_params->work_dir, furi_string_get_cstr(converted_fname), full_extracted_fname);
  207. bool success =
  208. archive_extract_current_file(archive, furi_string_get_cstr(full_extracted_fname));
  209. furi_string_free(converted_fname);
  210. furi_string_free(full_extracted_fname);
  211. return success ? 0 : -1;
  212. }
  213. bool tar_archive_unpack_to(
  214. TarArchive* archive,
  215. const char* destination,
  216. Storage_name_converter converter) {
  217. furi_assert(archive);
  218. TarArchiveDirectoryOpParams param = {
  219. .archive = archive,
  220. .work_dir = destination,
  221. .converter = converter,
  222. };
  223. FURI_LOG_I(TAG, "Restoring '%s'", destination);
  224. return (mtar_foreach(&archive->tar, archive_extract_foreach_cb, &param) == MTAR_ESUCCESS);
  225. };
  226. bool tar_archive_add_file(
  227. TarArchive* archive,
  228. const char* fs_file_path,
  229. const char* archive_fname,
  230. const int32_t file_size) {
  231. furi_assert(archive);
  232. uint8_t* file_buffer = malloc(FILE_BLOCK_SIZE);
  233. bool success = false;
  234. File* src_file = storage_file_alloc(archive->storage);
  235. uint8_t n_tries = FILE_OPEN_NTRIES;
  236. do {
  237. while(n_tries-- > 0) {
  238. if(storage_file_open(src_file, fs_file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  239. break;
  240. }
  241. FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", fs_file_path, n_tries);
  242. storage_file_close(src_file);
  243. furi_delay_ms(FILE_OPEN_RETRY_DELAY);
  244. }
  245. if(!storage_file_is_open(src_file) ||
  246. !tar_archive_file_add_header(archive, archive_fname, file_size)) {
  247. break;
  248. }
  249. success = true; // if file is empty, that's not an error
  250. uint16_t bytes_read = 0;
  251. while((bytes_read = storage_file_read(src_file, file_buffer, FILE_BLOCK_SIZE))) {
  252. success = tar_archive_file_add_data_block(archive, file_buffer, bytes_read);
  253. if(!success) {
  254. break;
  255. }
  256. }
  257. success = success && tar_archive_file_finalize(archive);
  258. } while(false);
  259. storage_file_free(src_file);
  260. free(file_buffer);
  261. return success;
  262. }
  263. bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const char* path_prefix) {
  264. furi_assert(archive);
  265. furi_check(path_prefix);
  266. File* directory = storage_file_alloc(archive->storage);
  267. FileInfo file_info;
  268. FURI_LOG_I(TAG, "Backing up '%s', '%s'", fs_full_path, path_prefix);
  269. char* name = malloc(MAX_NAME_LEN);
  270. bool success = false;
  271. do {
  272. if(!storage_dir_open(directory, fs_full_path)) {
  273. break;
  274. }
  275. while(true) {
  276. if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) {
  277. success = true; /* empty dir / no more files */
  278. break;
  279. }
  280. FuriString* element_name = furi_string_alloc();
  281. FuriString* element_fs_abs_path = furi_string_alloc();
  282. path_concat(fs_full_path, name, element_fs_abs_path);
  283. if(strlen(path_prefix)) {
  284. path_concat(path_prefix, name, element_name);
  285. } else {
  286. furi_string_set(element_name, name);
  287. }
  288. if(file_info.flags & FSF_DIRECTORY) {
  289. success =
  290. tar_archive_dir_add_element(archive, furi_string_get_cstr(element_name)) &&
  291. tar_archive_add_dir(
  292. archive,
  293. furi_string_get_cstr(element_fs_abs_path),
  294. furi_string_get_cstr(element_name));
  295. } else {
  296. success = tar_archive_add_file(
  297. archive,
  298. furi_string_get_cstr(element_fs_abs_path),
  299. furi_string_get_cstr(element_name),
  300. file_info.size);
  301. }
  302. furi_string_free(element_name);
  303. furi_string_free(element_fs_abs_path);
  304. if(!success) {
  305. break;
  306. }
  307. }
  308. } while(false);
  309. free(name);
  310. storage_file_free(directory);
  311. return success;
  312. }
  313. bool tar_archive_unpack_file(
  314. TarArchive* archive,
  315. const char* archive_fname,
  316. const char* destination) {
  317. furi_assert(archive);
  318. furi_assert(archive_fname);
  319. furi_assert(destination);
  320. if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) {
  321. return false;
  322. }
  323. return archive_extract_current_file(archive, destination);
  324. }