Volkov: добавить Sprinter Commander
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user