Skip to content

Commit 8468a71

Browse files
committed
FreeImage_Save uses tmp file to avoid corrupted target file states
1 parent 13543ad commit 8468a71

3 files changed

Lines changed: 89 additions & 25 deletions

File tree

Source/FreeImage/Plugin.cpp

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include <ctype.h>
3636
#endif // _WIN32
3737

38+
#include <filesystem>
39+
3840
#include "FreeImage.h"
3941
#include "Utilities.h"
4042
#include "FreeImageIO.h"
@@ -769,41 +771,95 @@ FreeImage_SaveToHandle(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, FreeImageIO *io, fi
769771
}
770772

771773

772-
FIBOOL DLL_CALLCONV
773-
FreeImage_Save(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const char *filename, int flags) {
774-
FreeImageIO io;
775-
SetDefaultIO(&io);
774+
namespace {
776775

777-
FIBOOL success{FALSE};
778-
if (auto *handle = fopen(filename, "w+b")) {
779-
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
780776

781-
fclose(handle);
782-
} else {
783-
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to open file %s", filename);
777+
std::filesystem::path MakeRandomSuffix()
778+
{
779+
std::stringstream strs{};
780+
strs << ".fitmp" << std::hex << static_cast<uint32_t>(std::rand());
781+
return strs.str();
784782
}
785783

786-
return success;
787-
}
784+
785+
std::filesystem::path MakeTmpName(const std::filesystem::path& target)
786+
{
787+
std::filesystem::path res{};
788+
789+
int attempts = 16;
790+
do {
791+
res = target.stem();
792+
res += MakeRandomSuffix();
793+
res += target.extension();
794+
795+
if (std::filesystem::status(res).type() != std::filesystem::file_type::not_found) {
796+
continue;
797+
}
798+
799+
} while (--attempts);
800+
801+
return res;
802+
}
803+
804+
805+
bool MoveOrCopy(const std::filesystem::path& oldp, const std::filesystem::path& newp)
806+
{
807+
std::error_code err{};
808+
std::filesystem::rename(oldp, newp, err);
809+
if (!err) {
810+
return true;
811+
}
812+
return std::filesystem::copy_file(oldp, newp);
813+
}
814+
815+
816+
FIBOOL SaveImpl(FREE_IMAGE_FORMAT fif, FIBITMAP* dib, const std::filesystem::path& filename, int flags) {
817+
FreeImageIO io;
818+
SetDefaultIO(&io);
819+
820+
FIBOOL success{ FALSE };
821+
822+
const auto tmpname = MakeTmpName(filename);
823+
if (auto* handle = FreeImage_FOpen(tmpname, "w+b")) {
824+
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
825+
fclose(handle);
826+
827+
if (success) {
828+
if (!MoveOrCopy(tmpname, filename)) {
829+
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to rename output file %s", filename.u8string().c_str());
830+
}
831+
}
832+
833+
std::filesystem::remove(tmpname);
834+
}
835+
else {
836+
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to open file %s", filename.u8string().c_str());
837+
}
838+
839+
return success;
840+
}
841+
842+
} // namespace
843+
788844

789845
FIBOOL DLL_CALLCONV
790-
FreeImage_SaveU(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const wchar_t *filename, int flags) {
791-
FreeImageIO io;
792-
SetDefaultIO(&io);
846+
FreeImage_Save(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const char *filename, int flags) {
847+
if (!dib || !filename) {
848+
return FALSE;
849+
}
850+
return SaveImpl(fif, dib, filename, flags);
851+
}
793852

794-
FIBOOL success{FALSE};
795-
#ifdef _WIN32
796-
if (auto *handle = _wfopen(filename, L"w+b")) {
797-
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
798853

799-
fclose(handle);
800-
} else {
801-
FreeImage_OutputMessageProc((int)fif, "FreeImage_SaveU: failed to open output file");
854+
FIBOOL DLL_CALLCONV
855+
FreeImage_SaveU(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const wchar_t *filename, int flags) {
856+
if (!dib || !filename) {
857+
return FALSE;
802858
}
803-
#endif
804-
return success;
859+
return SaveImpl(fif, dib, filename, flags);
805860
}
806861

862+
807863
// =====================================================================
808864
// Plugin construction + enable/disable functions
809865
// =====================================================================

TestAPI/MainTestSuite.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ int main(int argc, char *argv[]) {
8989
// test internal image types
9090
testImageType(width, height);
9191

92+
93+
auto bmp = FreeImage_AllocateT(FIT_COMPLEX, 128, 128, 128);
94+
FreeImage_Save(FIF_JPEG, bmp, "failed_to_save.jpg");
95+
FreeImage_Unload(bmp);
96+
97+
9298
#if FREEIMAGE_WITH_LIBJPEG
9399
// test the clone function
94100
testAllocateCloneUnload("exif.jpg");

TestAPI/testHeaderOnly.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ testExifRawFile(const char *lpszPathName, int load_flags, int save_flags) {
174174
uint8_t *value = (uint8_t*)FreeImage_GetTagValue(tag);
175175

176176
// save as jpeg : Exif data should be preserved
177-
FreeImage_Save(fif, dib, lpszDstPathName, save_flags);
177+
if (!FreeImage_Save(fif, dib, lpszDstPathName, save_flags)) {
178+
throw (1);
179+
}
178180

179181
// load and check Exif raw data
180182
fif = FreeImage_GetFileType(lpszDstPathName);

0 commit comments

Comments
 (0)