8e389c03f8
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
187 lines
6.4 KiB
C
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;
|
|
}
|