You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

416 lines
20 KiB

#ifndef _WIN32
#define _FILE_OFFSET_BITS 64
#endif
#include "disk.h"
#include "../../lib/debug_config.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <wchar.h>
#include <sys/stat.h>
#include <openssl/evp.h>
#ifndef _WIN32
#include <dirent.h>
#endif
static int disk_error(const char* operation, const char* path) {
int error = errno;
int status = error == ENOENT ? MF_NOT_FOUND : error == EEXIST ? MF_EXISTS :
error == ENOSPC ? MF_NO_SPACE : error == ENOTDIR ? MF_INVALID_PATH : MF_IO_ERROR;
if (error == ENOTEMPTY || (error == EEXIST && !strcmp(operation, "remove"))) status = MF_DIRECTORY_NOT_EMPTY;
#ifdef EDQUOT
if (error == EDQUOT) status = MF_NO_SPACE;
#endif
#ifdef ELOOP
if (error == ELOOP) status = MF_INVALID_PATH;
#endif
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: disk %s path=%s status=%s errno=%d (%s)", operation,
path ? path : "", mf_status_text(status), error, strerror(error));
if (status != MF_NOT_FOUND && status != MF_EXISTS)
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: disk %s path=%s errno=%d (%s)", operation, path ? path : "", error, strerror(error));
return status;
}
#ifdef _WIN32
struct disk_parent { HANDLE held[66]; unsigned count; wchar_t path[4096]; };
static int disk_wide(const char* path, wchar_t* wide, size_t cap) {
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, (int)cap)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: invalid UTF-8 path winerr=%lu", GetLastError()); return MF_INVALID_PATH;
}
return MF_OK;
}
static int disk_win_error(const char* operation) {
DWORD e = GetLastError();
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: %s winerr=%lu", operation, e);
if (e != ERROR_FILE_NOT_FOUND && e != ERROR_PATH_NOT_FOUND && e != ERROR_ALREADY_EXISTS && e != ERROR_FILE_EXISTS)
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: %s winerr=%lu", operation, e);
if (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) return MF_NOT_FOUND;
if (e == ERROR_ALREADY_EXISTS || e == ERROR_FILE_EXISTS) return MF_EXISTS;
if (e == ERROR_DISK_FULL || e == ERROR_HANDLE_DISK_FULL) return MF_NO_SPACE;
if (e == ERROR_DIR_NOT_EMPTY) return MF_DIRECTORY_NOT_EMPTY;
return MF_IO_ERROR;
}
static int disk_handle_info(HANDLE h, struct mf_disk_info* out) {
BY_HANDLE_FILE_INFORMATION i;
if (!GetFileInformationByHandle(h, &i)) return disk_win_error("GetFileInformationByHandle");
if ((i.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || i.nNumberOfLinks > 1) return MF_INVALID_PATH;
memset(out, 0, sizeof(*out));
out->type = i.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? MF_DIRECTORY : MF_FILE;
out->size = out->type == MF_FILE ? ((uint64_t)i.nFileSizeHigh << 32) | i.nFileSizeLow : 0;
uint64_t written = ((uint64_t)i.ftLastWriteTime.dwHighDateTime << 32) | i.ftLastWriteTime.dwLowDateTime;
out->mtime = written > 116444736000000000ULL ? (written - 116444736000000000ULL) / 10000000 : 0;
out->identity[0] = i.dwVolumeSerialNumber;
out->identity[1] = ((uint64_t)i.nFileIndexHigh << 32) | i.nFileIndexLow;
out->identity[2] = written;
out->identity[3] = ((uint64_t)i.ftCreationTime.dwHighDateTime << 32) | i.ftCreationTime.dwLowDateTime;
out->identity[4] = out->size;
return MF_OK;
}
static void disk_parent_close(struct disk_parent* p) {
while (p->count) if (!CloseHandle(p->held[--p->count])) disk_win_error("close directory");
}
/* Каждый родитель закреплён без SHARE_DELETE до завершения операции над листом. */
static int disk_parent_open(struct mf_disk_root* root, const char* path, struct disk_parent* p) {
memset(p, 0, sizeof(*p));
char full[4096];
int n = snprintf(full, sizeof(full), "%s/%s", root->path, path);
if (n < 0 || (size_t)n >= sizeof(full) || disk_wide(full, p->path, 4096)) return MF_INVALID_PATH;
size_t begin = strlen(root->path) + 1;
char relative[MF_PATH_MAX + 80];
if (strlen(path) >= sizeof(relative)) return MF_INVALID_PATH;
snprintf(relative, sizeof(relative), "%s", path);
for (char* s = relative; *s; s++) {
if (*s != '/') continue;
size_t at = begin + (size_t)(s - relative);
char saved = full[at]; full[at] = 0;
wchar_t dir[4096];
int result = disk_wide(full, dir, 4096); full[at] = saved;
if (result || p->count >= 66) { disk_parent_close(p); return MF_INVALID_PATH; }
HANDLE h = CreateFileW(dir, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (h == INVALID_HANDLE_VALUE) { result = disk_win_error("open parent"); disk_parent_close(p); return result; }
p->held[p->count++] = h;
struct mf_disk_info info;
result = disk_handle_info(h, &info);
if (result || info.type != MF_DIRECTORY) { disk_parent_close(p); return result ? result : MF_INVALID_PATH; }
}
return MF_OK;
}
int mf_disk_root_open(struct mf_disk_root* root, const char* path) {
wchar_t wide[4096];
if (strlen(path) >= sizeof(root->path) || disk_wide(path, wide, 4096)) return MF_INVALID_PATH;
if (!CreateDirectoryW(wide, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return disk_win_error("create root");
root->handle = CreateFileW(wide, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (root->handle == INVALID_HANDLE_VALUE) return disk_win_error("open root");
struct mf_disk_info info;
int rc = disk_handle_info(root->handle, &info);
if (rc || info.type != MF_DIRECTORY) { CloseHandle(root->handle); return rc ? rc : MF_INVALID_PATH; }
snprintf(root->path, sizeof(root->path), "%s", path);
return MF_OK;
}
void mf_disk_root_close(struct mf_disk_root* root) {
if (!CloseHandle(root->handle)) disk_win_error("close root");
}
int mf_disk_open(struct mf_disk_root* root, const char* path, int create, int writable, int* fd) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
HANDLE h = CreateFileW(p.path, GENERIC_READ | (writable ? GENERIC_WRITE : 0), FILE_SHARE_READ, NULL,
create ? CREATE_NEW : OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (h == INVALID_HANDLE_VALUE) rc = disk_win_error("open file");
else {
struct mf_disk_info i; rc = disk_handle_info(h, &i);
if (!rc && i.type != MF_FILE) rc = MF_INVALID_PATH;
if (!rc) {
*fd = _open_osfhandle((intptr_t)h, _O_BINARY | (writable ? _O_RDWR : _O_RDONLY));
if (*fd < 0) rc = disk_error("open fd", path);
}
if (rc) CloseHandle(h);
}
disk_parent_close(&p); return rc;
}
int mf_disk_stat(struct mf_disk_root* root, const char* path, struct mf_disk_info* info) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
HANDLE h = CreateFileW(p.path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (h == INVALID_HANDLE_VALUE) rc = disk_win_error("stat");
else { rc = disk_handle_info(h, info); CloseHandle(h); }
disk_parent_close(&p); return rc;
}
int mf_disk_info(int fd, struct mf_disk_info* info) { return disk_handle_info((HANDLE)_get_osfhandle(fd), info); }
int mf_disk_mkdir(struct mf_disk_root* root, const char* path, int* created) {
if (created) *created = 0;
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
if (!CreateDirectoryW(p.path, NULL)) rc = disk_win_error("mkdir");
else if (created) *created = 1;
disk_parent_close(&p); return rc;
}
int mf_disk_remove(struct mf_disk_root* root, const char* path, int directory) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
if (!(directory ? RemoveDirectoryW(p.path) : DeleteFileW(p.path))) rc = disk_win_error("remove");
disk_parent_close(&p); return rc;
}
int mf_disk_rename(struct mf_disk_root* root, const char* from, const char* to) {
struct disk_parent a, b; int rc = disk_parent_open(root, from, &a);
if (rc) return rc;
rc = disk_parent_open(root, to, &b);
if (!rc) {
if (!MoveFileExW(a.path, b.path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) rc = disk_win_error("publish/rename");
disk_parent_close(&b);
}
disk_parent_close(&a); return rc;
}
int mf_disk_list(struct mf_disk_root* root, const char* path, mf_disk_entry_fn cb, void* arg) {
char probe[MF_PATH_MAX + 100]; snprintf(probe, sizeof(probe), "%s%s_", path, *path ? "/" : "");
struct disk_parent p; int rc = disk_parent_open(root, probe, &p);
if (rc) return rc;
size_t n = wcslen(p.path); p.path[n - 1] = L'*';
WIN32_FIND_DATAW data;
HANDLE find = FindFirstFileW(p.path, &data);
if (find == INVALID_HANDLE_VALUE) {
rc = GetLastError() == ERROR_FILE_NOT_FOUND ? MF_OK : disk_win_error("list");
disk_parent_close(&p); return rc;
}
for (;;) {
if (!wcscmp(data.cFileName, L".") || !wcscmp(data.cFileName, L"..")) goto next;
char name[MF_NAME_MAX * 4 + 1], child[MF_PATH_MAX + 100];
if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, data.cFileName, -1, name, sizeof(name), NULL, NULL)) {
rc = MF_INVALID_PATH; break;
}
if (snprintf(child, sizeof(child), "%s/%s", path, name) >= (int)sizeof(child)) { rc = MF_INVALID_PATH; break; }
struct mf_disk_info info; rc = mf_disk_stat(root, child, &info);
if (rc) break;
int result = cb(arg, name, &info);
if (result) { rc = result < 0 ? -result : MF_OK; break; }
next:
if (!FindNextFileW(find, &data)) {
if (GetLastError() != ERROR_NO_MORE_FILES) rc = disk_win_error("FindNextFileW");
break;
}
}
FindClose(find); disk_parent_close(&p); return rc;
}
int mf_disk_resize(int fd, uint64_t size) {
if (_chsize_s(fd, size)) return disk_error("resize", NULL);
return MF_OK;
}
int mf_disk_sync(int fd) { return _commit(fd) ? disk_error("sync", NULL) : MF_OK; }
int mf_disk_close(int fd) { return _close(fd) ? disk_error("close", NULL) : MF_OK; }
int mf_disk_local_open(const char* path, int create, int* fd) {
wchar_t wide[4096]; if (disk_wide(path, wide, 4096)) return MF_INVALID_PATH;
*fd = _wopen(wide, _O_BINARY | (create ? _O_RDWR | _O_CREAT | _O_EXCL : _O_RDONLY), _S_IREAD | _S_IWRITE);
return *fd < 0 ? disk_error("local open", path) : MF_OK;
}
int mf_disk_local_publish(const char* from, const char* to) {
wchar_t a[4096], b[4096]; if (disk_wide(from, a, 4096) || disk_wide(to, b, 4096)) return MF_INVALID_PATH;
return MoveFileExW(a, b, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) ? MF_OK : disk_win_error("local publish");
}
int mf_disk_local_remove(const char* path) {
wchar_t wide[4096]; if (disk_wide(path, wide, 4096)) return MF_INVALID_PATH;
if (DeleteFileW(wide) || GetLastError() == ERROR_FILE_NOT_FOUND) return MF_OK;
return disk_win_error("local remove");
}
#else
struct disk_parent { int fd; char leaf[MF_NAME_MAX + 1]; };
static int disk_parent_open(struct mf_disk_root* root, const char* path, struct disk_parent* p) {
char copy[MF_PATH_MAX + 80];
if (strlen(path) >= sizeof(copy)) return MF_INVALID_PATH;
snprintf(copy, sizeof(copy), "%s", path);
p->fd = dup(root->fd);
if (p->fd < 0) return disk_error("dup root", path);
char* component = copy;
for (char* slash; (slash = strchr(component, '/')); component = slash + 1) {
*slash = 0;
int next = openat(p->fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
close(p->fd);
if (next < 0) return disk_error("open parent", path);
p->fd = next;
}
if (strlen(component) > MF_NAME_MAX) { close(p->fd); return MF_INVALID_PATH; }
snprintf(p->leaf, sizeof(p->leaf), "%s", component);
return MF_OK;
}
static int disk_posix_info(const struct stat* st, struct mf_disk_info* i) {
if ((!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) || (S_ISREG(st->st_mode) && st->st_nlink != 1)) return MF_INVALID_PATH;
memset(i, 0, sizeof(*i)); i->type = S_ISDIR(st->st_mode) ? MF_DIRECTORY : MF_FILE;
i->size = i->type == MF_FILE ? (uint64_t)st->st_size : 0; i->mtime = (uint64_t)st->st_mtime;
i->identity[0] = st->st_dev; i->identity[1] = st->st_ino;
i->identity[2] = st->st_mtim.tv_sec; i->identity[3] = st->st_mtim.tv_nsec;
i->identity[4] = st->st_ctim.tv_sec; i->identity[5] = st->st_ctim.tv_nsec;
return MF_OK;
}
int mf_disk_root_open(struct mf_disk_root* root, const char* path) {
if (strlen(path) >= sizeof(root->path)) return MF_INVALID_PATH;
if (mkdir(path, 0700) && errno != EEXIST) return disk_error("create root", path);
root->fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (root->fd < 0) return disk_error("open root", path);
snprintf(root->path, sizeof(root->path), "%s", path); return MF_OK;
}
void mf_disk_root_close(struct mf_disk_root* root) { if (close(root->fd)) disk_error("close root", root->path); }
int mf_disk_open(struct mf_disk_root* root, const char* path, int create, int writable, int* fd) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
*fd = openat(p.fd, p.leaf, O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK | (writable ? O_RDWR : O_RDONLY) |
(create ? O_CREAT | O_EXCL : 0), 0600);
if (*fd < 0) rc = disk_error("open file", path);
else {
struct mf_disk_info info; rc = mf_disk_info(*fd, &info);
if (!rc && info.type != MF_FILE) rc = MF_INVALID_PATH;
if (rc) { close(*fd); *fd = -1; }
}
close(p.fd); return rc;
}
int mf_disk_stat(struct mf_disk_root* root, const char* path, struct mf_disk_info* info) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
struct stat st;
if (fstatat(p.fd, p.leaf[0] ? p.leaf : ".", &st, AT_SYMLINK_NOFOLLOW)) rc = disk_error("stat", path);
else rc = disk_posix_info(&st, info);
close(p.fd); return rc;
}
int mf_disk_info(int fd, struct mf_disk_info* info) {
struct stat st;
return fstat(fd, &st) ? disk_error("fstat", NULL) : disk_posix_info(&st, info);
}
int mf_disk_mkdir(struct mf_disk_root* root, const char* path, int* created) {
if (created) *created = 0;
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
if (mkdirat(p.fd, p.leaf, 0700)) rc = disk_error("mkdir", path);
else {
if (created) *created = 1;
if (fsync(p.fd)) rc = disk_error("sync mkdir", path);
}
close(p.fd); return rc;
}
int mf_disk_remove(struct mf_disk_root* root, const char* path, int directory) {
struct disk_parent p; int rc = disk_parent_open(root, path, &p);
if (rc) return rc;
if (unlinkat(p.fd, p.leaf, directory ? AT_REMOVEDIR : 0)) rc = disk_error("remove", path);
else if (fsync(p.fd)) rc = disk_error("sync remove", path);
close(p.fd); return rc;
}
int mf_disk_rename(struct mf_disk_root* root, const char* from, const char* to) {
struct disk_parent a, b; int rc = disk_parent_open(root, from, &a);
if (rc) return rc;
rc = disk_parent_open(root, to, &b);
if (!rc) {
if (renameat(a.fd, a.leaf, b.fd, b.leaf)) rc = disk_error("rename", to);
else if (fsync(b.fd) || fsync(a.fd)) rc = disk_error("sync rename", to);
close(b.fd);
}
close(a.fd); return rc;
}
int mf_disk_list(struct mf_disk_root* root, const char* path, mf_disk_entry_fn cb, void* arg) {
char probe[MF_PATH_MAX + 100];
if (snprintf(probe, sizeof(probe), "%s%s_", path, *path ? "/" : "") >= (int)sizeof(probe)) return MF_INVALID_PATH;
struct disk_parent p; int rc = disk_parent_open(root, probe, &p);
if (rc) return rc;
DIR* dir = fdopendir(p.fd);
if (!dir) { rc = disk_error("list", path); close(p.fd); return rc; }
struct dirent* e;
for (;;) {
errno = 0; e = readdir(dir);
if (!e) { if (errno) rc = disk_error("readdir", path); break; }
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
struct stat st; struct mf_disk_info info;
if (fstatat(dirfd(dir), e->d_name, &st, AT_SYMLINK_NOFOLLOW)) { rc = disk_error("list stat", e->d_name); break; }
rc = disk_posix_info(&st, &info);
if (rc) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: unsafe directory entry path=%s name=%s", path, e->d_name); break; }
int result = cb(arg, e->d_name, &info);
if (result) { rc = result < 0 ? -result : MF_OK; break; }
}
if (closedir(dir) && !rc) rc = disk_error("closedir", path);
return rc;
}
int mf_disk_resize(int fd, uint64_t size) { return ftruncate(fd, (off_t)size) ? disk_error("resize", NULL) : MF_OK; }
int mf_disk_sync(int fd) { return fsync(fd) ? disk_error("sync", NULL) : MF_OK; }
int mf_disk_close(int fd) { return close(fd) ? disk_error("close", NULL) : MF_OK; }
int mf_disk_local_open(const char* path, int create, int* fd) {
*fd = open(path, O_CLOEXEC | O_NOFOLLOW | (create ? O_RDWR | O_CREAT | O_EXCL : O_RDONLY), 0600);
return *fd < 0 ? disk_error("local open", path) : MF_OK;
}
int mf_disk_local_publish(const char* from, const char* to) { return rename(from, to) ? disk_error("local publish", to) : MF_OK; }
int mf_disk_local_remove(const char* path) { return !unlink(path) || errno == ENOENT ? MF_OK : disk_error("local remove", path); }
#endif
int mf_disk_read(int fd, uint64_t offset, void* data, size_t size) {
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: disk read fd=%d offset=%llu bytes=%zu", fd, (unsigned long long)offset, size);
size_t done = 0;
while (done < size) {
#ifdef _WIN32
if (_lseeki64(fd, (int64_t)(offset + done), SEEK_SET) < 0) return disk_error("seek read", NULL);
int n = _read(fd, (uint8_t*)data + done, (unsigned)(size - done));
#else
ssize_t n = pread(fd, (uint8_t*)data + done, size - done, (off_t)(offset + done));
#endif
if (n < 0 && errno == EINTR) continue;
if (n <= 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: short/read error offset=%llu size=%zu done=%zu errno=%d",
(unsigned long long)offset, size, done, errno); return MF_IO_ERROR; }
done += (size_t)n;
}
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: disk read done fd=%d bytes=%zu", fd, done);
return MF_OK;
}
int mf_disk_write(int fd, uint64_t offset, const void* data, size_t size) {
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: disk write fd=%d offset=%llu bytes=%zu", fd, (unsigned long long)offset, size);
size_t done = 0;
while (done < size) {
#ifdef _WIN32
if (_lseeki64(fd, (int64_t)(offset + done), SEEK_SET) < 0) return disk_error("seek write", NULL);
int n = _write(fd, (const uint8_t*)data + done, (unsigned)(size - done));
#else
ssize_t n = pwrite(fd, (const uint8_t*)data + done, size - done, (off_t)(offset + done));
#endif
if (n < 0 && errno == EINTR) continue;
if (n <= 0) return disk_error("write", NULL);
done += (size_t)n;
}
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: disk write done fd=%d bytes=%zu", fd, done);
return MF_OK;
}
/* Полное хеширование выполняется только worker, с ограниченным буфером. */
int mf_disk_hash(int fd, uint64_t size, uint8_t hash[32]) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: hash start fd=%d bytes=%llu", fd, (unsigned long long)size);
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
int rc = MF_IO_ERROR;
if (!ctx || EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) goto out;
uint8_t buffer[65536];
for (uint64_t off = 0; off < size;) {
size_t n = size - off < sizeof(buffer) ? (size_t)(size - off) : sizeof(buffer);
rc = mf_disk_read(fd, off, buffer, n);
if (rc || EVP_DigestUpdate(ctx, buffer, n) != 1) { rc = MF_IO_ERROR; goto out; }
off += n;
}
unsigned n = 0;
rc = EVP_DigestFinal_ex(ctx, hash, &n) == 1 && n == 32 ? MF_OK : MF_IO_ERROR;
out:
EVP_MD_CTX_free(ctx);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: hash done fd=%d status=%s", fd, mf_status_text(rc));
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: hash failed size=%llu status=%d", (unsigned long long)size, rc);
return rc;
}