diff --git a/lib/Makefile.am b/lib/Makefile.am index 224ce970..1fcc7c71 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -15,6 +15,8 @@ libuasync_a_SOURCES = \ socket_compat.h \ platform_compat.c \ platform_compat.h \ + file_io.c \ + file_io.h \ getmyip.c \ getmyip.h \ swm_min.c \ diff --git a/src/video/video.c b/src/video/video.c index 69a0dacc..043eb28b 100644 --- a/src/video/video.c +++ b/src/video/video.c @@ -5,6 +5,7 @@ #include "../../lib/mem.h" #include "../../lib/u_async.h" #include "../../lib/platform_compat.h" +#include "../../lib/file_io.h" #include #include @@ -232,148 +233,6 @@ int video_needs_transcode(const struct video_info* info) { return 0; } -struct vc_file { - char temporary[VIDEO_PATH_MAX + 64]; - FILE* file; -}; - -#ifdef _WIN32 -static int vc_wide(const char* path, wchar_t* wide) { - if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, VIDEO_PATH_MAX + 64)) return 0; - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid UTF-8 path=%s winerr=%lu", path, GetLastError()); - return -1; -} -#endif - -/* Проверить обычные файлы и их идентичность до записи; повторить перед публикацией. */ -static int vc_paths(const char* source, const char* destination) { - if (!source || !destination || !*source || !*destination || - strlen(source) >= VIDEO_PATH_MAX || strlen(destination) >= VIDEO_PATH_MAX) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid/oversized source or destination"); - return -1; - } -#ifdef _WIN32 - wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64]; - if (vc_wide(source, from) || vc_wide(destination, to)) return -1; - HANDLE input = CreateFileW(from, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - BY_HANDLE_FILE_INFORMATION a, b; - int valid = input != INVALID_HANDLE_VALUE && GetFileInformationByHandle(input, &a) && - GetFileType(input) == FILE_TYPE_DISK && !(a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - if (input != INVALID_HANDLE_VALUE) CloseHandle(input); - if (!valid) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid source=%s winerr=%lu", source, GetLastError()); return -1; } - DWORD attributes = GetFileAttributesW(to); - if (attributes == INVALID_FILE_ATTRIBUTES) { - DWORD error = GetLastError(); - if (error == ERROR_FILE_NOT_FOUND) return 0; - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination inspection failed path=%s winerr=%lu", destination, error); - return -1; - } - if (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is directory/reparse point path=%s", destination); return -1; - } - HANDLE output = CreateFileW(to, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - valid = output != INVALID_HANDLE_VALUE && GetFileInformationByHandle(output, &b) && GetFileType(output) == FILE_TYPE_DISK; - if (output != INVALID_HANDLE_VALUE) CloseHandle(output); - if (!valid || (a.dwVolumeSerialNumber == b.dwVolumeSerialNumber && - a.nFileIndexHigh == b.nFileIndexHigh && a.nFileIndexLow == b.nFileIndexLow)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination aliases source or cannot be inspected path=%s", destination); return -1; - } -#else - struct stat input, output; - if (stat(source, &input) || !S_ISREG(input.st_mode)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video source is not readable regular file path=%s errno=%d", source, errno); return -1; - } - if (lstat(destination, &output)) { - if (errno == ENOENT) return 0; - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination stat failed path=%s errno=%d", destination, errno); return -1; - } - if (!S_ISREG(output.st_mode) || (input.st_dev == output.st_dev && input.st_ino == output.st_ino)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is not distinct regular file source=%s destination=%s", source, destination); - return -1; - } -#endif - return 0; -} - -/* Создать уникальный соседний файл эксклюзивно; FILE остаётся у задачи до finish/discard. */ -static int vc_file_open(struct vc_file* output, const char* destination) { - for (int attempt = 0; attempt < 8; attempt++) { - uint8_t random[16]; char suffix[33]; - if (random_bytes(random, sizeof(random))) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temp randomness failed"); return -1; } - for (unsigned i = 0; i < sizeof(random); i++) snprintf(suffix + i * 2, 3, "%02x", random[i]); - snprintf(output->temporary, sizeof(output->temporary), "%s.tmp.%s", destination, suffix); -#ifdef _WIN32 - wchar_t wide[VIDEO_PATH_MAX + 64]; - if (vc_wide(output->temporary, wide)) { output->temporary[0] = 0; return -1; } - int fd = _wopen(wide, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE); -#else - int fd = open(output->temporary, O_CREAT | O_EXCL | O_RDWR, 0600); -#endif - if (fd < 0) { - output->temporary[0] = 0; - if (errno == EEXIST) continue; - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary creation failed destination=%s errno=%d", destination, errno); - return -1; - } -#ifdef _WIN32 - output->file = _fdopen(fd, "wb+"); - if (!output->file) _close(fd); -#else - output->file = fdopen(fd, "wb+"); - if (!output->file) close(fd); -#endif - if (!output->file) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video fdopen failed errno=%d", errno); return -1; } - DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video temporary opened path=%s", output->temporary); - return 0; - } - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary collision limit destination=%s", destination); - return -1; -} - -static int vc_file_finish(struct vc_file* output) { - int error = fflush(output->file) ? errno : 0; - if (fclose(output->file) && !error) error = errno; - output->file = NULL; - if (error) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video flush/close failed path=%s errno=%d", output->temporary, error); return -1; } - return 0; -} - -static void vc_file_discard(struct vc_file* output) { - if (output->file) { - if (fclose(output->file)) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video cleanup close failed errno=%d", errno); - output->file = NULL; - } - if (!output->temporary[0]) return; -#ifdef _WIN32 - wchar_t wide[VIDEO_PATH_MAX + 64]; - int error = vc_wide(output->temporary, wide) ? -1 : _wremove(wide); -#else - int error = remove(output->temporary); -#endif - if (error && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video temporary cleanup failed path=%s errno=%d", output->temporary, errno); - output->temporary[0] = 0; -} - -/* Последняя способная отказать операция: после rename результат уже опубликован. */ -static int vc_file_publish(struct vc_file* output, const char* source, const char* destination) { - if (vc_paths(source, destination)) return -1; -#ifdef _WIN32 - wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64]; - if (vc_wide(output->temporary, from) || vc_wide(destination, to)) return -1; - if (!MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s winerr=%lu", destination, GetLastError()); return -1; - } -#else - if (rename(output->temporary, destination)) { - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s errno=%d", destination, errno); return -1; - } -#endif - output->temporary[0] = 0; - DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "video published destination=%s", destination); - return 0; -} struct vc_stream { int index; @@ -391,7 +250,7 @@ struct vc_stream { struct vc_ctx { char source[VIDEO_PATH_MAX], destination[VIDEO_PATH_MAX]; - struct vc_file file; + struct file_io_temp file; void (*done)(void*, int, const struct video_info*); void* arg; int error, remux; @@ -409,7 +268,7 @@ static int vc_io_write(void* opaque, const uint8_t* bytes, int length) { #else static int vc_io_write(void* opaque, uint8_t* bytes, int length) { #endif - struct vc_file* file = opaque; + struct file_io_temp* file = opaque; if (fwrite(bytes, 1, length, file->file) == (size_t)length) return length; int error = errno ? errno : EIO; DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 write failed path=%s requested=%d errno=%d", file->temporary, length, error); @@ -417,7 +276,7 @@ static int vc_io_write(void* opaque, uint8_t* bytes, int length) { } static int64_t vc_io_seek(void* opaque, int64_t offset, int whence) { - struct vc_file* file = opaque; + struct file_io_temp* file = opaque; if (whence == AVSEEK_SIZE) { #ifdef _WIN32 struct _stat64 info; @@ -459,7 +318,7 @@ static int vc_output_close(struct vc_ctx* ctx) { avio_context_free(&ctx->output->pb); if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 buffered output failed error=%s", vc_errstr(error)); } - if (ctx->file.file && vc_file_finish(&ctx->file)) error = -1; + if (ctx->file.file && file_io_temp_finish(&ctx->file)) error = -1; return error < 0 ? -1 : 0; } @@ -854,7 +713,7 @@ static int vc_run(struct vc_ctx* ctx) { static void vc_thread_entry(void* raw) { struct vc_ctx* ctx = raw; video_ffmpeg_init(); - if (vc_paths(ctx->source, ctx->destination) || vc_file_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; } + if (file_io_paths(ctx->source, ctx->destination) || file_io_temp_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; } struct stat info; if (!stat(ctx->source, &info)) ctx->source_size = info.st_size; int error = vc_run(ctx); @@ -873,8 +732,8 @@ static void vc_thread_entry(void* raw) { static void vc_done_trampoline(void* raw, int error) { struct vc_ctx* ctx = raw; if (!error) error = ctx->error; - if (!error && vc_file_publish(&ctx->file, ctx->source, ctx->destination)) error = -1; - vc_file_discard(&ctx->file); + if (!error && file_io_temp_publish(&ctx->file, ctx->source, ctx->destination)) error = -1; + file_io_temp_discard(&ctx->file); vc_progress_set(-1); vc_active_set(0); DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video completion destination=%s error=%d", ctx->destination, error); @@ -929,11 +788,11 @@ static int vc_first_frame(AVFormatContext* format, AVCodecContext* decoder, int } int video_extract_thumbnail(const char* src, const char* dst, int max_w) { - if (vc_paths(src, dst)) return -1; + if (file_io_paths(src, dst)) return -1; if (max_w <= 0) max_w = 320; if (max_w < 2) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail width must be >=2"); return -1; } video_ffmpeg_init(); - struct vc_file output = {0}; + struct file_io_temp output = {0}; AVFormatContext* format = NULL; AVCodecContext* decoder = NULL; AVCodecContext* encoder = NULL; AVFrame* input = NULL; AVFrame* frame = NULL; AVPacket* packet = NULL; @@ -983,17 +842,17 @@ int video_extract_thumbnail(const char* src, const char* dst, int max_w) { } if (error < 0) goto done; phase = "write JPEG"; - if (vc_file_open(&output, dst)) { error = AVERROR(EIO); goto done; } + if (file_io_temp_open(&output, dst)) { error = AVERROR(EIO); goto done; } if (fwrite(packet->data, 1, packet->size, output.file) != (size_t)packet->size) { error = AVERROR(errno ? errno : EIO); goto done; } - if (vc_file_finish(&output)) { error = AVERROR(EIO); goto done; } + if (file_io_temp_finish(&output)) { error = AVERROR(EIO); goto done; } phase = "publish JPEG"; - error = vc_file_publish(&output, src, dst) ? AVERROR(EIO) : 0; + error = file_io_temp_publish(&output, src, dst) ? AVERROR(EIO) : 0; done: if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail failed phase=%s source=%s destination=%s error=%s", phase, src, dst, vc_errstr(error)); - vc_file_discard(&output); + file_io_temp_discard(&output); sws_freeContext(scale); av_frame_free(&input); av_frame_free(&frame); av_packet_free(&packet); avcodec_free_context(&decoder); avcodec_free_context(&encoder); avformat_close_input(&format); return error < 0 ? -1 : 0;