Files
Sprinter-SDCC/applications/Volkov/tests/p5_copy_faults.c
T
snark13 8e389c03f8 Volkov: добавить Sprinter Commander
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места.

Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
2026-09-10 10:45:30 +03:00

187 lines
6.4 KiB
C

/*
* p5_copy_faults.c — детерминированные отрицательные сценарии copy-job.
*
* Это не подмена интерактивного теста F5: программа вызывает то же банковое
* ядро копирования и останавливает его в состояниях, которые невозможно
* надёжно поймать клавишей с точностью до одного sc_copy_step().
*/
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <conio.h>
#include <sprinter_mem.h>
#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;
}