/* * test_spiffs.c * * Created on: Jun 19, 2013 * Author: petera */ #include #include #include #include "params_test.h" #include "spiffs.h" #include "spiffs_nucleus.h" #include "testrunner.h" #include "test_spiffs.h" #include #include #include #include #include #define AREA(x) area[(x) - addr_offset] static unsigned char area[PHYS_FLASH_SIZE]; static u32_t addr_offset = 0; static int erases[PHYS_FLASH_SIZE/SECTOR_SIZE]; static char _path[256]; static u32_t bytes_rd = 0; static u32_t bytes_wr = 0; static u32_t reads = 0; static u32_t writes = 0; static u32_t error_after_bytes_written = 0; static u32_t error_after_bytes_read = 0; static char error_after_bytes_written_once_only = 0; static char error_after_bytes_read_once_only = 0; static char log_flash_ops = 1; static u32_t fs_check_fixes = 0; spiffs __fs; static u8_t _work[LOG_PAGE*2]; static u8_t _fds[FD_BUF_SIZE]; static u8_t _cache[CACHE_BUF_SIZE]; static int check_valid_flash = 1; #define TEST_PATH "test_data/" char *make_test_fname(const char *name) { sprintf(_path, "%s%s", TEST_PATH, name); return _path; } void clear_test_path() { DIR *dp; struct dirent *ep; dp = opendir(TEST_PATH); if (dp != NULL) { while ((ep = readdir(dp))) { if (ep->d_name[0] != '.') { sprintf(_path, "%s%s", TEST_PATH, ep->d_name); remove(_path); } } closedir(dp); } } static s32_t _read(u32_t addr, u32_t size, u8_t *dst) { if (log_flash_ops) { bytes_rd += size; reads++; if (error_after_bytes_read > 0 && bytes_rd >= error_after_bytes_read) { if (error_after_bytes_read_once_only) { error_after_bytes_read = 0; } return SPIFFS_ERR_TEST; } } if (addr < __fs.cfg.phys_addr) { printf("FATAL read addr too low %08x < %08x\n", addr, SPIFFS_PHYS_ADDR); exit(0); } if (addr + size > __fs.cfg.phys_addr + __fs.cfg.phys_size) { printf("FATAL read addr too high %08x + %08x > %08x\n", addr, size, SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE); exit(0); } memcpy(dst, &AREA(addr), size); return 0; } static s32_t _write(u32_t addr, u32_t size, u8_t *src) { int i; //printf("wr %08x %i\n", addr, size); if (log_flash_ops) { bytes_wr += size; writes++; if (error_after_bytes_written > 0 && bytes_wr >= error_after_bytes_written) { if (error_after_bytes_written_once_only) { error_after_bytes_written = 0; } return SPIFFS_ERR_TEST; } } if (addr < __fs.cfg.phys_addr) { printf("FATAL write addr too low %08x < %08x\n", addr, SPIFFS_PHYS_ADDR); exit(0); } if (addr + size > __fs.cfg.phys_addr + __fs.cfg.phys_size) { printf("FATAL write addr too high %08x + %08x > %08x\n", addr, size, SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE); exit(0); } for (i = 0; i < size; i++) { if (((addr + i) & (__fs.cfg.log_page_size-1)) != offsetof(spiffs_page_header, flags)) { if (check_valid_flash && ((AREA(addr + i) ^ src[i]) & src[i])) { printf("trying to write %02x to %02x at addr %08x\n", src[i], AREA(addr + i), addr+i); spiffs_page_ix pix = (addr + i) / LOG_PAGE; dump_page(&__fs, pix); return -1; } } AREA(addr + i) &= src[i]; } return 0; } static s32_t _erase(u32_t addr, u32_t size) { if (addr & (__fs.cfg.phys_erase_block-1)) { printf("trying to erase at addr %08x, out of boundary\n", addr); return -1; } if (size & (__fs.cfg.phys_erase_block-1)) { printf("trying to erase at with size %08x, out of boundary\n", size); return -1; } erases[(addr-__fs.cfg.phys_addr)/__fs.cfg.phys_erase_block]++; memset(&AREA(addr), 0xff, size); return 0; } void hexdump_mem(u8_t *b, u32_t len) { while (len--) { if ((((intptr_t)b)&0x1f) == 0) { printf("\n"); } printf("%02x", *b++); } printf("\n"); } void hexdump(u32_t addr, u32_t len) { int remainder = (addr % 32) == 0 ? 0 : 32 - (addr % 32); u32_t a; for (a = addr - remainder; a < addr+len; a++) { if ((a & 0x1f) == 0) { if (a != addr) { printf(" "); int j; for (j = 0; j < 32; j++) { if (a-32+j < addr) printf(" "); else { printf("%c", (AREA(a-32+j) < 32 || AREA(a-32+j) >= 0x7f) ? '.' : AREA(a-32+j)); } } } printf("%s %08x: ", a<=addr ? "":"\n", a); } if (a < addr) { printf(" "); } else { printf("%02x", AREA(a)); } } int j; printf(" "); for (j = 0; j < 32; j++) { if (a-32+j < addr) printf(" "); else { printf("%c", (AREA(a-32+j) < 32 || AREA(a-32+j) >= 0x7f) ? '.' : AREA(a-32+j)); } } printf("\n"); } void dump_page(spiffs *fs, spiffs_page_ix p) { printf("page %04x ", p); u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, p); if (p % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { // obj lu page printf("OBJ_LU"); } else { u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , p)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, p) * sizeof(spiffs_obj_id); spiffs_obj_id obj_id = *((spiffs_obj_id *)&AREA(obj_id_addr)); // data page spiffs_page_header *ph = (spiffs_page_header *)&AREA(addr); printf("DATA %04x:%04x ", obj_id, ph->span_ix); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_FINAL) == 0) ? "FIN " : "fin "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_DELET) == 0) ? "DEL " : "del "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_INDEX) == 0) ? "IDX " : "idx "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_USED) == 0) ? "USD " : "usd "); printf("%s ", ((ph->flags & SPIFFS_PH_FLAG_IXDELE) == 0) ? "IDL " : "idl "); if (obj_id & SPIFFS_OBJ_ID_IX_FLAG) { // object index printf("OBJ_IX"); if (ph->span_ix == 0) { printf("_HDR "); spiffs_page_object_ix_header *oix_hdr = (spiffs_page_object_ix_header *)&AREA(addr); printf("'%s' %i bytes type:%02x", oix_hdr->name, oix_hdr->size, oix_hdr->type); } } else { // data page printf("CONTENT"); } } printf("\n"); u32_t len = fs->cfg.log_page_size; hexdump(addr, len); } void area_write(u32_t addr, u8_t *buf, u32_t size) { int i; for (i = 0; i < size; i++) { AREA(addr + i) = *buf++; } } void area_read(u32_t addr, u8_t *buf, u32_t size) { int i; for (i = 0; i < size; i++) { *buf++ = AREA(addr + i); } } void dump_erase_counts(spiffs *fs) { spiffs_block_ix bix; printf(" BLOCK |\n"); printf(" AGE COUNT|\n"); for (bix = 0; bix < fs->block_count; bix++) { printf("----%3i ----|", bix); } printf("\n"); for (bix = 0; bix < fs->block_count; bix++) { spiffs_obj_id erase_mark; _spiffs_rd(fs, 0, 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix), sizeof(spiffs_obj_id), (u8_t *)&erase_mark); if (erases[bix] == 0) { printf(" |"); } else { printf("%7i %4i|", (fs->max_erase_count - erase_mark), erases[bix]); } } printf("\n"); } void dump_flash_access_stats() { printf(" RD: %10i reads %10i bytes %10i avg bytes/read\n", reads, bytes_rd, reads == 0 ? 0 : (bytes_rd / reads)); printf(" WR: %10i writes %10i bytes %10i avg bytes/write\n", writes, bytes_wr, writes == 0 ? 0 : (bytes_wr / writes)); } static u32_t old_perc = 999; static void spiffs_check_cb_f(spiffs_check_type type, spiffs_check_report report, u32_t arg1, u32_t arg2) { /* if (report == SPIFFS_CHECK_PROGRESS && old_perc != arg1) { old_perc = arg1; printf("CHECK REPORT: "); switch(type) { case SPIFFS_CHECK_LOOKUP: printf("LU "); break; case SPIFFS_CHECK_INDEX: printf("IX "); break; case SPIFFS_CHECK_PAGE: printf("PA "); break; } printf("%i%%\n", arg1 * 100 / 256); }*/ if (report != SPIFFS_CHECK_PROGRESS) { if (report != SPIFFS_CHECK_ERROR) fs_check_fixes++; printf(" check: "); switch (type) { case SPIFFS_CHECK_INDEX: printf("INDEX "); break; case SPIFFS_CHECK_LOOKUP: printf("LOOKUP "); break; case SPIFFS_CHECK_PAGE: printf("PAGE "); break; default: printf("???? "); break; } if (report == SPIFFS_CHECK_ERROR) { printf("ERROR %i", arg1); } else if (report == SPIFFS_CHECK_DELETE_BAD_FILE) { printf("DELETE BAD FILE %04x", arg1); } else if (report == SPIFFS_CHECK_DELETE_ORPHANED_INDEX) { printf("DELETE ORPHANED INDEX %04x", arg1); } else if (report == SPIFFS_CHECK_DELETE_PAGE) { printf("DELETE PAGE %04x", arg1); } else if (report == SPIFFS_CHECK_FIX_INDEX) { printf("FIX INDEX %04x:%04x", arg1, arg2); } else if (report == SPIFFS_CHECK_FIX_LOOKUP) { printf("FIX INDEX %04x:%04x", arg1, arg2); } else { printf("??"); } printf("\n"); } } void fs_set_addr_offset(u32_t offset) { addr_offset = offset; } s32_t fs_mount_specific(u32_t phys_addr, u32_t phys_size, u32_t phys_sector_size, u32_t log_block_size, u32_t log_page_size) { spiffs_config c; c.hal_erase_f = _erase; c.hal_read_f = _read; c.hal_write_f = _write; c.log_block_size = log_block_size; c.log_page_size = log_page_size; c.phys_addr = phys_addr; c.phys_erase_block = phys_sector_size; c.phys_size = phys_size; return SPIFFS_mount(&__fs, &c, _work, _fds, sizeof(_fds), _cache, sizeof(_cache), spiffs_check_cb_f); } void fs_reset_specific(u32_t addr_offset, u32_t phys_addr, u32_t phys_size, u32_t phys_sector_size, u32_t log_block_size, u32_t log_page_size) { fs_set_addr_offset(addr_offset); memset(area, 0xcc, sizeof(area)); memset(&AREA(phys_addr), 0xff, phys_size); memset(&__fs, 0, sizeof(__fs)); memset(erases,0,sizeof(erases)); memset(_cache,0,sizeof(_cache)); s32_t res = fs_mount_specific(phys_addr, phys_size, phys_sector_size, log_block_size, log_page_size); #if SPIFFS_USE_MAGIC if (res == SPIFFS_OK) { SPIFFS_unmount(&__fs); } res = SPIFFS_format(&__fs); if (res != SPIFFS_OK) { printf("format failed, %i\n", SPIFFS_errno(&__fs)); } res = SPIFFS_mount(&__fs, &c, _work, _fds, sizeof(_fds), _cache, sizeof(_cache), spiffs_check_cb_f); if (res != SPIFFS_OK) { printf("mount failed, %i\n", SPIFFS_errno(&__fs)); } #endif clear_flash_ops_log(); log_flash_ops = 1; fs_check_fixes = 0; } void fs_reset() { fs_reset_specific(0, SPIFFS_PHYS_ADDR, SPIFFS_FLASH_SIZE, SECTOR_SIZE, LOG_BLOCK, LOG_PAGE); } void set_flash_ops_log(int enable) { log_flash_ops = enable; } void clear_flash_ops_log() { bytes_rd = 0; bytes_wr = 0; reads = 0; writes = 0; error_after_bytes_read = 0; error_after_bytes_written = 0; } u32_t get_flash_ops_log_read_bytes() { return bytes_rd; } u32_t get_flash_ops_log_write_bytes() { return bytes_wr; } void invoke_error_after_read_bytes(u32_t b, char once_only) { error_after_bytes_read = b; error_after_bytes_read_once_only = once_only; } void invoke_error_after_write_bytes(u32_t b, char once_only) { error_after_bytes_written = b; error_after_bytes_written_once_only = once_only; } void fs_set_validate_flashing(int i) { check_valid_flash = i; } void real_assert(int c, const char *n, const char *file, int l) { if (c == 0) { printf("ASSERT: %s %s @ %i\n", (n ? n : ""), file, l); printf("fs errno:%i\n", __fs.err_code); exit(0); } } int read_and_verify(char *name) { s32_t res; int fd = SPIFFS_open(&__fs, name, SPIFFS_RDONLY, 0); if (fd < 0) { printf(" read_and_verify: could not open file %s\n", name); return fd; } return read_and_verify_fd(fd, name); } int read_and_verify_fd(spiffs_file fd, char *name) { s32_t res; int pfd = open(make_test_fname(name), O_RDONLY); spiffs_stat s; res = SPIFFS_fstat(&__fs, fd, &s); if (res < 0) { printf(" read_and_verify: could not stat file %s\n", name); return res; } if (s.size == 0) { SPIFFS_close(&__fs, fd); close(pfd); return 0; } //printf("verifying %s, len %i\n", name, s.size); int offs = 0; u8_t buf_d[256]; u8_t buf_v[256]; while (offs < s.size) { int read_len = MIN(s.size - offs, sizeof(buf_d)); res = SPIFFS_read(&__fs, fd, buf_d, read_len); if (res < 0) { printf(" read_and_verify: could not read file %s offs:%i len:%i filelen:%i\n", name, offs, read_len, s.size); return res; } int pres = read(pfd, buf_v, read_len); (void)pres; //printf("reading offs:%i len:%i spiffs_res:%i posix_res:%i\n", offs, read_len, res, pres); int i; int veri_ok = 1; for (i = 0; veri_ok && i < read_len; i++) { if (buf_d[i] != buf_v[i]) { printf("file verification mismatch @ %i, %02x %c != %02x %c\n", offs+i, buf_d[i], buf_d[i], buf_v[i], buf_v[i]); int j = MAX(0, i-16); int k = MIN(sizeof(buf_d), i+16); k = MIN(s.size-offs, k); int l; for (l = j; l < k; l++) { printf("%c", buf_d[l] > 31 ? buf_d[l] : '.'); } printf("\n"); for (l = j; l < k; l++) { printf("%c", buf_v[l] > 31 ? buf_v[l] : '.'); } printf("\n"); veri_ok = 0; } } if (!veri_ok) { SPIFFS_close(&__fs, fd); close(pfd); printf("data mismatch\n"); return -1; } offs += read_len; } SPIFFS_close(&__fs, fd); close(pfd); return 0; } static void test_on_stop(test *t) { printf(" spiffs errno:%i\n", SPIFFS_errno(&__fs)); #if SPIFFS_TEST_VISUALISATION SPIFFS_vis(FS); #endif } void memrand(u8_t *b, int len) { int i; for (i = 0; i < len; i++) { b[i] = rand(); } } int test_create_file(char *name) { spiffs_stat s; spiffs_file fd; int res = SPIFFS_creat(FS, name, 0); CHECK_RES(res); fd = SPIFFS_open(FS, name, SPIFFS_RDONLY, 0); CHECK(fd >= 0); res = SPIFFS_fstat(FS, fd, &s); CHECK_RES(res); CHECK(strcmp((char*)s.name, name) == 0); CHECK(s.size == 0); SPIFFS_close(FS, fd); return 0; } int test_create_and_write_file(char *name, int size, int chunk_size) { int res; spiffs_file fd; printf(" create and write %s", name); res = test_create_file(name); if (res < 0) { printf(" failed creation, %i\n",res); } CHECK(res >= 0); fd = SPIFFS_open(FS, name, SPIFFS_APPEND | SPIFFS_RDWR, 0); if (res < 0) { printf(" failed open, %i\n",res); } CHECK(fd >= 0); int pfd = open(make_test_fname(name), O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); int offset = 0; int mark = 0; while (offset < size) { int len = MIN(size-offset, chunk_size); if (offset > mark) { mark += size/16; printf("."); fflush(stdout); } u8_t *buf = malloc(len); memrand(buf, len); res = SPIFFS_write(FS, fd, buf, len); write(pfd, buf, len); free(buf); if (res < 0) { printf("\n error @ offset %i, res %i\n", offset, res); } offset += len; CHECK(res >= 0); } printf("\n"); close(pfd); spiffs_stat stat; res = SPIFFS_fstat(FS, fd, &stat); if (res < 0) { printf(" failed fstat, %i\n",res); } CHECK(res >= 0); if (stat.size != size) { printf(" failed size, %i != %i\n", stat.size, size); } CHECK(stat.size == size); SPIFFS_close(FS, fd); return 0; } #if SPIFFS_CACHE #if SPIFFS_CACHE_STATS static u32_t chits_tot = 0; static u32_t cmiss_tot = 0; #endif #endif void _setup_test_only() { fs_set_validate_flashing(1); test_init(test_on_stop); } void _setup() { fs_reset(); _setup_test_only(); } void _teardown() { printf(" free blocks : %i of %i\n", (FS)->free_blocks, (FS)->block_count); printf(" pages allocated : %i\n", (FS)->stats_p_allocated); printf(" pages deleted : %i\n", (FS)->stats_p_deleted); #if SPIFFS_GC_STATS printf(" gc runs : %i\n", (FS)->stats_gc_runs); #endif #if SPIFFS_CACHE #if SPIFFS_CACHE_STATS chits_tot += (FS)->cache_hits; cmiss_tot += (FS)->cache_misses; printf(" cache hits : %i (sum %i)\n", (FS)->cache_hits, chits_tot); printf(" cache misses : %i (sum %i)\n", (FS)->cache_misses, cmiss_tot); printf(" cache utiliz : %f\n", ((float)chits_tot/(float)(chits_tot + cmiss_tot))); chits_tot = 0; cmiss_tot = 0; #endif #endif dump_flash_access_stats(); clear_flash_ops_log(); #if SPIFFS_GC_STATS if ((FS)->stats_gc_runs > 0) #endif dump_erase_counts(FS); printf(" fs consistency check:\n"); SPIFFS_check(FS); clear_test_path(); //hexdump_mem(&AREA(SPIFFS_PHYS_ADDR - 16), 32); //hexdump_mem(&AREA(SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE - 16), 32); } u32_t tfile_get_size(tfile_size s) { switch (s) { case EMPTY: return 0; case SMALL: return SPIFFS_DATA_PAGE_SIZE(FS)/2; case MEDIUM: return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); case LARGE: return (FS)->cfg.phys_size/3; } return 0; } int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files, int dbg) { int res; tfile *tfiles = malloc(sizeof(tfile) * max_concurrent_files); memset(tfiles, 0, sizeof(tfile) * max_concurrent_files); int run = 0; int cur_config_ix = 0; char name[32]; while (run < max_runs) { if (dbg) printf(" run %i/%i\n", run, max_runs); int i; for (i = 0; i < max_concurrent_files; i++) { sprintf(name, "file%i_%i", (1+run), i); tfile *tf = &tfiles[i]; if (tf->state == 0 && cur_config_ix < cfg_count) { // create a new file strcpy(tf->name, name); tf->state = 1; tf->cfg = cfgs[cur_config_ix]; int size = tfile_get_size(tf->cfg.tsize); if (dbg) printf(" create new %s with cfg %i/%i, size %i\n", name, (1+cur_config_ix), cfg_count, size); if (tf->cfg.tsize == EMPTY) { res = SPIFFS_creat(FS, name, 0); CHECK_RES(res); int pfd = open(make_test_fname(name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); close(pfd); int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_RDWR, 0); CHECK(fd > 0); tf->fd = fd; } else { int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0; spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0); CHECK(fd > 0); extra_flags = tf->cfg.ttype == APPENDED ? O_APPEND : 0; int pfd = open(make_test_fname(name), extra_flags | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); tf->fd = fd; u8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, fd, buf, size); CHECK_RES(res); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(name); CHECK_RES(res); } cur_config_ix++; } else if (tf->state > 0) { // hande file lifecycle switch (tf->cfg.ttype) { case UNTAMPERED: { break; } case APPENDED: { if (dbg) printf(" appending %s\n", tf->name); int size = SPIFFS_DATA_PAGE_SIZE(FS)*3; u8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, tf->fd, buf, size); CHECK_RES(res); int pfd = open(make_test_fname(tf->name), O_APPEND | O_RDWR); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } case MODIFIED: { if (dbg) printf(" modify %s\n", tf->name); spiffs_stat stat; res = SPIFFS_fstat(FS, tf->fd, &stat); CHECK_RES(res); int size = stat.size / tf->cfg.tlife + SPIFFS_DATA_PAGE_SIZE(FS)/3; int offs = (stat.size / tf->cfg.tlife) * tf->state; res = SPIFFS_lseek(FS, tf->fd, offs, SPIFFS_SEEK_SET); CHECK_RES(res); u8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, tf->fd, buf, size); CHECK_RES(res); int pfd = open(make_test_fname(tf->name), O_RDWR); lseek(pfd, offs, SEEK_SET); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } case REWRITTEN: { if (tf->fd > 0) { SPIFFS_close(FS, tf->fd); } if (dbg) printf(" rewriting %s\n", tf->name); spiffs_file fd = SPIFFS_open(FS, tf->name, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0); CHECK(fd > 0); int pfd = open(make_test_fname(tf->name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); tf->fd = fd; int size = tfile_get_size(tf->cfg.tsize); u8_t *buf = malloc(size); memrand(buf, size); res = SPIFFS_write(FS, fd, buf, size); CHECK_RES(res); write(pfd, buf, size); close(pfd); free(buf); res = read_and_verify(tf->name); CHECK_RES(res); break; } } tf->state++; if (tf->state > tf->cfg.tlife) { // file outlived its time, kill it if (tf->fd > 0) { SPIFFS_close(FS, tf->fd); } if (dbg) printf(" removing %s\n", tf->name); res = read_and_verify(tf->name); CHECK_RES(res); res = SPIFFS_remove(FS, tf->name); CHECK_RES(res); remove(make_test_fname(tf->name)); memset(tf, 0, sizeof(tf)); } } } run++; } free(tfiles); return 0; }