/* * p5_copy_faults.c — детерминированные отрицательные сценарии copy-job. * * Это не подмена интерактивного теста F5: программа вызывает то же банковое * ядро копирования и останавливает его в состояниях, которые невозможно * надёжно поймать клавишей с точностью до одного sc_copy_step(). */ #include #include #include #include #include #include #include "sc_copy.h" #include "sc_exec.h" static ScCopyJob job; static int result_fd = -1; static uint8_t failures; static void log_text(const char *text) { const char *end = text; uint16_t length; cputs(text); while (*end != '\0') end++; length = (uint16_t)(end - text); if (result_fd >= 0) (void)write(result_fd, text, length); } static void check(uint8_t condition, const char *name) { if (condition) { log_text("PASS "); } else { log_text("FAIL "); failures++; } log_text(name); log_text("\r\n"); } static uint8_t path_absent(const char *path) { int fd = open(path, O_RDONLY); if (fd < 0) return 1u; (void)close(fd); return 0u; } static int write_small(const char *path, const char *data, uint16_t size) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC); int result = 0; if (fd < 0) return -1; if (write(fd, data, size) != (int)size) result = -1; if (close(fd) != 0) result = -1; return result; } int main(void) { uint16_t total_before = 0u; uint16_t free_before = 0u; uint16_t total_after = 0u; uint16_t free_after = 0u; uint8_t block; uint8_t page; int step; textattr(0x07u); clrscr_attr(0x07u); cputs("P5 copy-job fault matrix\r\n\r\n"); result_fd = open("D:\\RESULT.TXT", O_WRONLY | O_CREAT | O_TRUNC); mem_info(&total_before, &free_before); block = mem_alloc_pages(1u); if (block == 0u) { log_text("FAIL EMM allocation\r\n"); if (result_fd >= 0) (void)close(result_fd); return 1; } page = mem_get_page(block, 0u); errno = 0; step = sc_platform_exec("D:\\MISSING.EXE"); check(step < 0 && errno == ENOENT, "missing EXE errno 3"); /* Temp создаётся раньше открытия source: ENOENT обязан убрать его. */ check(sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "MISSING.BIN", 4096ul, page) != 0 && job.state == SC_COPY_ERROR && path_absent("D:\\TARGET\\MISSING.BIN"), "missing source cleanup"); /* Отмена до первого чтения. */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "BEGIN.BIN", 8192ul, page); if (step == 0) sc_copy_cancel(&job); check(step == 0 && job.state == SC_COPY_CANCELLED && job.done == 0ul && path_absent("D:\\TARGET\\BEGIN.BIN"), "cancel before first read"); /* Последний блок записан, но rename ещё не выполнялся: точка pre-commit. */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "LATE.BIN", 4096ul, page); if (step == 0) step = sc_copy_step(&job); check(step == SC_COPY_STEP_MORE && job.state == SC_COPY_RUNNING && job.done == job.total && path_absent("D:\\TARGET\\LATE.BIN"), "last block stops before commit"); sc_copy_cancel(&job); check(job.state == SC_COPY_CANCELLED && path_absent("D:\\TARGET\\LATE.BIN"), "cancel at pre-commit"); /* Закрытый source fd даёт реальную ошибку bank_read_page(). */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "READERR.BIN", 4096ul, page); if (step == 0) { (void)close(job.source_fd); job.source_fd = -1; step = sc_copy_step(&job); } check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && path_absent("D:\\TARGET\\READERR.BIN"), "read error cleanup"); /* Реальный DSS READONLY: temp повторно открыт только для чтения, поэтому * WRITE обязан вернуть errno 8, а job — удалить файл после close. */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "RONLY.BIN", 4096ul, page); if (step == 0) { (void)close(job.temp_fd); job.temp_fd = open(job.temp_path, O_RDONLY); if (job.temp_fd >= 0) { step = sc_copy_step(&job); } else { sc_copy_cancel(&job); step = -2; } } check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && job.error == EROFS && path_absent("D:\\TARGET\\RONLY.BIN"), "readonly write cleanup"); /* Удаляем уже записанный temp перед commit: RENAME обязан отказать. */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "RENAME.BIN", 4096ul, page); if (step == 0) step = sc_copy_step(&job); if (step == SC_COPY_STEP_MORE) { (void)close(job.temp_fd); job.temp_fd = -1; (void)unlink(job.temp_path); step = sc_copy_step(&job); } check(step == SC_COPY_STEP_ERROR && job.state == SC_COPY_ERROR && path_absent("D:\\TARGET\\RENAME.BIN"), "rename error cleanup"); /* Имитируем гонку: target появился после begin, но до commit. */ step = sc_copy_begin(&job, "D:\\SOURCE", "D:\\TARGET", "RACE.BIN", 4096ul, page); if (step == 0) step = sc_copy_step(&job); if (step == SC_COPY_STEP_MORE && write_small("D:\\TARGET\\RACE.BIN", "KEEP", 4u) == 0) { step = sc_copy_step(&job); } check(step == SC_COPY_STEP_ERROR && job.error == EEXIST, "target race refuses overwrite"); mem_free_block(block); mem_info(&total_after, &free_after); check(total_before == total_after && free_before == free_after, "EMM restored"); if (failures == 0u) log_text("ALL PASS\r\n"); if (result_fd >= 0) (void)close(result_fd); cputs("\r\nPress Enter to return to DSS.\r\n"); while (kbhit()) (void)getkey(); (void)getkey(); return failures; }