Preserve files on copy errors and check media primitive failures
This commit is contained in:
1 parent
85d96fc857
commit
f399ded5ba
1 file changed
+162
-105
+162
-105
@@ -112,130 +112,187 @@ void media_async_submit(struct media_async* ma, struct UASYNC* ua,
|
||||
job->next = ma->jobs; ma->jobs = job;
|
||||
}
|
||||
|
||||
/* ─── примитивы (вызываются из worker thread) ─── */
|
||||
|
||||
/* Синхронный SHA256; результат не публикуется при ошибке чтения/close. */
|
||||
int ma_sha256_file(const char* path, uint8_t hash_out[32]) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_sha256_file: cannot open %s", path); return -1; }
|
||||
|
||||
if (!hash_out) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "SHA256 requires output"); return -1; }
|
||||
memset(hash_out, 0, 32);
|
||||
FILE* file = file_io_open_read(path);
|
||||
if (!file) return -1;
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
|
||||
uint8_t buf[65536];
|
||||
size_t rd;
|
||||
while ((rd = fread(buf, 1, sizeof(buf), f)) > 0) SHA256_Update(&ctx, buf, rd);
|
||||
int error = ferror(f);
|
||||
if (fclose(f)) error = 1;
|
||||
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_sha256_file: read/close failed path=%s", path); return -1; }
|
||||
SHA256_Final(hash_out, &ctx);
|
||||
int error = SHA256_Init(&ctx) != 1;
|
||||
uint8_t buffer[65536]; size_t count;
|
||||
while (!error && (count = fread(buffer, 1, sizeof(buffer), file)) > 0)
|
||||
if (SHA256_Update(&ctx, buffer, count) != 1) error = 1;
|
||||
if (ferror(file)) error = 1;
|
||||
if (fclose(file)) error = 1;
|
||||
if (!error && SHA256_Final(hash_out, &ctx) != 1) error = 1;
|
||||
if (error) {
|
||||
memset(hash_out, 0, 32);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "SHA256 read/digest/close failed path=%s errno=%d", path, errno); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ma_sign_block(const uint8_t* ed25519_privkey, const uint8_t* data, size_t len, uint8_t sig_out[64]) {
|
||||
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, ed25519_privkey, 32);
|
||||
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_sign_block: EVP_PKEY_new_raw_private_key failed"); return -1; }
|
||||
|
||||
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx) { EVP_PKEY_free(pkey); return -1; }
|
||||
|
||||
if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_sign_block: EVP_DigestSignInit failed");
|
||||
EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); return -1;
|
||||
/* Подпись публикуется только после успешного EVP_DigestSign. */
|
||||
int ma_sign_block(const uint8_t* key, const uint8_t* data, size_t len, uint8_t signature[64]) {
|
||||
if (signature) memset(signature, 0, 64);
|
||||
if (!key || (!data && len) || !signature) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Ed25519 invalid key/data/output length=%zu", len); return -1;
|
||||
}
|
||||
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, key, 32);
|
||||
if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Ed25519 key allocation failed"); return -1; }
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Ed25519 context allocation failed"); EVP_PKEY_free(pkey); return -1; }
|
||||
uint8_t result[64]; size_t size = sizeof(result);
|
||||
int ok = EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) == 1 && EVP_DigestSign(ctx, result, &size, data, len) == 1;
|
||||
EVP_MD_CTX_free(ctx); EVP_PKEY_free(pkey);
|
||||
if (!ok || size != sizeof(result)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "Ed25519 signing failed length=%zu", size); return -1;
|
||||
}
|
||||
memcpy(signature, result, sizeof(result));
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t siglen = 64;
|
||||
int rc = EVP_DigestSign(mdctx, sig_out, &siglen, data, len);
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
if (rc != 1 || siglen != 64) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_sign_block: EVP_DigestSign failed rc=%d len=%zu", rc, siglen);
|
||||
/* Источник открыт до сравнения идентичности; назначение меняется только после полной записи и закрытия. */
|
||||
int ma_copy_file(const char* source, const char* destination) {
|
||||
FILE* input = file_io_open_read(source);
|
||||
if (!input) return -1;
|
||||
struct file_io_temp output = {0};
|
||||
int status = file_io_paths(source, destination);
|
||||
uint64_t total = 0;
|
||||
if (status == 0 && file_io_temp_open(&output, destination)) status = -1;
|
||||
if (status == 0) {
|
||||
uint8_t buffer[65536]; size_t count;
|
||||
while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) {
|
||||
if (fwrite(buffer, 1, count, output.file) != count) { status = -1; break; }
|
||||
total += count;
|
||||
}
|
||||
if (ferror(input)) status = -1;
|
||||
}
|
||||
if (fclose(input)) status = -1;
|
||||
if (status == 0 && file_io_temp_finish(&output)) status = -1;
|
||||
if (status == 0 && file_io_temp_publish(&output, source, destination)) status = -1;
|
||||
file_io_temp_discard(&output);
|
||||
if (status < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "copy failed source=%s destination=%s bytes=%llu errno=%d",
|
||||
source, destination ? destination : "(null)", (unsigned long long)total, errno);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "copy complete source=%s destination=%s bytes=%llu same_file=%d",
|
||||
source, destination, (unsigned long long)total, status == 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ma_copy_file(const char* src, const char* dst) {
|
||||
if (!src || !dst) return -1;
|
||||
if (strcmp(src, dst) == 0) return 0;
|
||||
|
||||
FILE* s = fopen(src, "rb");
|
||||
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_copy_file: cannot open src %s", src); return -1; }
|
||||
|
||||
FILE* d = fopen(dst, "wb");
|
||||
if (!d) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_copy_file: cannot create dst %s", dst); fclose(s); return -1; }
|
||||
|
||||
uint8_t buf[65536];
|
||||
size_t rd;
|
||||
while ((rd = fread(buf, 1, sizeof(buf), s)) > 0) {
|
||||
if (fwrite(buf, 1, rd, d) != rd) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_copy_file: write error at %s", dst);
|
||||
fclose(s); fclose(d); return -1;
|
||||
}
|
||||
int64_t ma_file_size(const char* path) {
|
||||
struct file_io_info info;
|
||||
int status = file_io_inspect(path, 1, &info);
|
||||
if (status == 1) { DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "file size missing path=%s", path); return -1; }
|
||||
if (status < 0) return -1;
|
||||
if (info.type != FILE_IO_REGULAR) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file size requires regular file path=%s type=%d", path, info.type); return -1;
|
||||
}
|
||||
int error = ferror(s);
|
||||
if (fclose(s)) error = 1;
|
||||
if (fclose(d)) error = 1;
|
||||
if (error) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "ma_copy_file: read/close failed src=%s dst=%s", src, dst);
|
||||
return error ? -1 : 0;
|
||||
return info.size;
|
||||
}
|
||||
|
||||
int ma_file_size(const char* path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0) return -1;
|
||||
return (int)(st.st_size > 0 ? st.st_size : 0);
|
||||
int ma_uuid(uint8_t uuid_out[16]) {
|
||||
if (!uuid_out) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "UUID requires output"); return -1; }
|
||||
int rc = RAND_bytes(uuid_out, 16);
|
||||
if (rc != 1) {
|
||||
memset(uuid_out, 0, 16);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "UUID RNG failed rc=%d", rc); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ma_uuid(uint8_t uuid_out[16]) {
|
||||
RAND_bytes(uuid_out, 16);
|
||||
}
|
||||
|
||||
/* ─── Рекурсивный обход каталога (кроссплатформенный) ─── */
|
||||
|
||||
struct ma_walk_ctx { ma_dir_walk_cb cb; void* arg; };
|
||||
static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, unsigned depth);
|
||||
|
||||
#ifndef _WIN32
|
||||
static void ma_walk_recursive(const char* dir, struct ma_walk_ctx* wc) {
|
||||
DIR* d = opendir(dir);
|
||||
if (!d) return;
|
||||
struct dirent* e;
|
||||
while ((e = readdir(d)) != NULL) {
|
||||
const char* n = e->d_name;
|
||||
if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) continue;
|
||||
char full[4096];
|
||||
snprintf(full, sizeof(full), "%s/%s", dir, n);
|
||||
struct stat st;
|
||||
if (lstat(full, &st) != 0) continue;
|
||||
if (S_ISDIR(st.st_mode)) ma_walk_recursive(full, wc);
|
||||
else if (S_ISREG(st.st_mode)) wc->cb(wc->arg, full, (int64_t)st.st_size);
|
||||
/* Одна запись: ссылки/устройства пропускаются, ошибки делают обход неполным. */
|
||||
static int ma_walk_entry(const char* directory, const char* name, struct ma_walk_ctx* ctx, unsigned depth) {
|
||||
if (!strcmp(name, ".") || !strcmp(name, "..")) return 0;
|
||||
char full[FILE_IO_PATH_MAX];
|
||||
int length = snprintf(full, sizeof(full), "%s/%s", directory, name);
|
||||
if (length < 0 || (size_t)length >= sizeof(full)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry path too long directory=%s name=%s", directory, name); return -1;
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
#else
|
||||
static void ma_walk_recursive(const char* dir, struct ma_walk_ctx* wc) {
|
||||
char pattern[4096];
|
||||
snprintf(pattern, sizeof(pattern), "%s/*", dir);
|
||||
WIN32_FIND_DATAA fd;
|
||||
HANDLE h = FindFirstFileA(pattern, &fd);
|
||||
if (h == INVALID_HANDLE_VALUE) return;
|
||||
do {
|
||||
const char* n = fd.cFileName;
|
||||
if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) continue;
|
||||
char full[4096];
|
||||
snprintf(full, sizeof(full), "%s/%s", dir, n);
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ma_walk_recursive(full, wc);
|
||||
else {
|
||||
LARGE_INTEGER sz; sz.HighPart = fd.nFileSizeHigh; sz.LowPart = fd.nFileSizeLow;
|
||||
wc->cb(wc->arg, full, (int64_t)sz.QuadPart);
|
||||
}
|
||||
} while (FindNextFileA(h, &fd));
|
||||
FindClose(h);
|
||||
}
|
||||
#endif
|
||||
|
||||
int ma_dir_walk(const char* dir, ma_dir_walk_cb cb, void* arg) {
|
||||
if (!dir || !dir[0] || !cb) return -1;
|
||||
struct ma_walk_ctx wc; wc.cb = cb; wc.arg = arg;
|
||||
ma_walk_recursive(dir, &wc);
|
||||
struct file_io_info info;
|
||||
if (file_io_inspect(full, 0, &info)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry inspection failed path=%s", full); return -1;
|
||||
}
|
||||
if (info.type == FILE_IO_DIRECTORY) return ma_walk_recursive(full, ctx, depth + 1);
|
||||
if (info.type == FILE_IO_REGULAR) ctx->cb(ctx->arg, full, info.size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ограниченный по глубине обход без перехода по конечным ссылкам. */
|
||||
static int ma_walk_recursive(const char* directory, struct ma_walk_ctx* ctx, unsigned depth) {
|
||||
if (depth > MA_WALK_MAX_DEPTH) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory depth limit path=%s depth=%u", directory, depth); return -1;
|
||||
}
|
||||
struct file_io_info info;
|
||||
if (file_io_inspect(directory, 0, &info)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory inspection failed path=%s", directory); return -1;
|
||||
}
|
||||
if (info.type == FILE_IO_LINK) return 0;
|
||||
if (info.type != FILE_IO_DIRECTORY) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory walk requires directory path=%s type=%d", directory, info.type); return -1;
|
||||
}
|
||||
int status = 0;
|
||||
#ifdef _WIN32
|
||||
char pattern[FILE_IO_PATH_MAX]; wchar_t wide[FILE_IO_PATH_MAX];
|
||||
int length = snprintf(pattern, sizeof(pattern), "%s/*", directory);
|
||||
if (length < 0 || (size_t)length >= sizeof(pattern)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory search path too long path=%s", directory); return -1;
|
||||
}
|
||||
if (file_io_wide(pattern, wide)) return -1;
|
||||
WIN32_FIND_DATAW data;
|
||||
HANDLE find = FindFirstFileW(wide, &data);
|
||||
if (find == INVALID_HANDLE_VALUE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_FILE_NOT_FOUND) return 0; // Открытый выше каталог пуст.
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory open failed path=%s winerr=%lu", directory, error); return -1;
|
||||
}
|
||||
do {
|
||||
char name[FILE_IO_PATH_MAX];
|
||||
if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, data.cFileName, -1, name, sizeof(name), NULL, NULL)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory entry UTF-8 conversion failed path=%s winerr=%lu", directory, GetLastError());
|
||||
status = -1; break;
|
||||
}
|
||||
if (ma_walk_entry(directory, name, ctx, depth)) { status = -1; break; }
|
||||
if (!FindNextFileW(find, &data)) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_NO_MORE_FILES) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory read failed path=%s winerr=%lu", directory, error); status = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
if (!FindClose(find)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory close failed path=%s winerr=%lu", directory, GetLastError()); status = -1;
|
||||
}
|
||||
#else
|
||||
DIR* dir = opendir(directory);
|
||||
if (!dir) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory open failed path=%s errno=%d", directory, errno); return -1; }
|
||||
while (1) {
|
||||
errno = 0;
|
||||
struct dirent* entry = readdir(dir);
|
||||
if (!entry) {
|
||||
if (errno) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory read failed path=%s errno=%d", directory, errno); status = -1; }
|
||||
break;
|
||||
}
|
||||
if (ma_walk_entry(directory, entry->d_name, ctx, depth)) { status = -1; break; }
|
||||
}
|
||||
if (closedir(dir)) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory close failed path=%s errno=%d", directory, errno); status = -1; }
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
int ma_dir_walk(const char* directory, ma_dir_walk_cb callback, void* arg) {
|
||||
if (!directory || !*directory || !callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "directory walk invalid path/callback"); return -1;
|
||||
}
|
||||
struct ma_walk_ctx ctx = {.cb = callback, .arg = arg};
|
||||
int result = ma_walk_recursive(directory, &ctx, 0);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "directory walk finished path=%s result=%d", directory, result);
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user