79630547

Date: 2025-05-20 13:35:08
Score: 0.5
Natty:
Report link

Here's my try on the matter.

Important things to note

Well, pure C++ is better, but it's not a requirement. Portability would require explanation, and include at least any-reasonable-UNIX'ish OS and Windows. Preferably also Haiku, QNX, VXWorks... – einpoklum

I haven't used any external libraries, and everything under there should be C++11 compliant.

I've opted to use some cstring functions, as well as fopen/fclose for file interactions, but feel free to substitute them with whatever suits you. I've mainly chosen them because it returns a FILE* on all systems (didn't want to make a huge mess with Windows HANDLEs)

The Windows part relies on Windows API calls. There's no way to avoid this, you need it to determine the conventional temporary folder.

The non-Windows part is for all POSIX-compliant systems, with Haiku, QNX and VXWorks should be, along with most if not all Linux-based systems and MacOS. I couldn't test on all those systems, but if you can find any incompatibility, please notify me.

How this works

Using preprocessor directives, we detect the OS family we're targetting. This allows us to define different code for Windows and POSIX-complicant systems.

Both sides define:

A global std::map<FILE*, std::string> named tempFileMap is defined to keep track of the opened temporary files, so they can be disposed of in case the program terminates.

It is therefore important for you to call closeTempFile(FILE*) if you want to get rid of a temporary file yourself, and NOT fclose(FILE*). closeTempFile(FILE*) will call fclose(FILE*) internally, but will also delete the file and remove it from the list. Calling fclose(FILE*) yourself will result in the file being closed twice!

Windows side

POSIX-compilant OSs side

Code

#include <string>

// std::filesystem::temp_directory_path is from C++17 (https://en.cppreference.com/w/cpp/filesystem/temp_directory_path), we need to call APIs ourselves to comply with C++11

void closureRoutine();

#include <map>
#include <vector>

std::map<FILE*, std::string> tempFileMap = {};

#if defined(_WIN32) || defined(WIN32)

#include <Windows.h>
#include <signal.h>

#if !defined(MAX_PATH)
#error MAX_PATH is not defined
#endif

#define PATH_MAX (MAX_PATH + 1)

void getTempDir(char* pathBuff) { // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2a
    GetTempPath2A(PATH_MAX, pathBuff); // Has a trailing backslash by default
}

void openTempFileFromFolder(const char* folderPath, char* outFilePath, FILE** outFileStream) {
    GUID guid;
    while (true) {
        if (CoCreateGuid(&guid) != S_OK)
            break;

        sprintf_s(outFilePath, PATH_MAX, "%s%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", folderPath, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);

        // If the file doesn't exist yet, create it and return. Microsoft says a GUID cannot be generated twice, but for the same of mimicking the behaviour of POSIX's mkstemp, and for absolute rigorousness, let's check anyway.
        DWORD tmpFileAttrib = GetFileAttributesA(outFilePath); // https://stackoverflow.com/a/6218957/9399492
        if (tmpFileAttrib == INVALID_FILE_ATTRIBUTES) {
            fopen_s(outFileStream, outFilePath, "w+");
            tempFileMap.insert({ *outFileStream, outFilePath });
            return;
        }
    }

    *outFileStream = nullptr;
}

void closeTempFile(FILE* file) {
    std::string filename = tempFileMap[file];
    tempFileMap.erase(file);

    fclose(file);
    DeleteFileA(filename.c_str());
}

void __closureRoutine(int sig) {
    closureRoutine();
}

void hookFunctionToClose() {
    signal(SIGABRT, __closureRoutine);
    atexit(closureRoutine); // Naturally triggered by CTRL+C on Windows
}

#elif defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) // Should work for any POSIX-compliant system, including MacOS (https://stackoverflow.com/a/11351171/9399492)

#include <cstring>
#include <cstdlib>
#include <limits.h>
#include <signal.h>
#include <stdio.h>

#if !defined(PATH_MAX)
#error PATH_MAX is not defined
#endif

void getTempDir(char* pathBuff) { // https://stackoverflow.com/a/14626770/9399492
    char* tmp = getenv("TMPDIR");
    if (!tmp) {
        tmp = getenv("TMP");
        if (!tmp) {
            tmp = getenv("TEMP");
            if (!tmp) {
                tmp = getenv("TEMPDIR");
            }
        }
    }

    sprintf(pathBuff, "%s/", tmp ? tmp : "/tmp"); // Add the trailing slash
}

void openTempFileFromFolder(const char* folderPath, char* outFilePath, FILE** outFileStream) {
    sprintf(outFilePath, "%sXXXXXX", folderPath);
    int fd = mkstemp(outFilePath); // Creates a file with umask 600. It is guaranteed to be unused, since it should keep trying to create a file with a new name until it can (https://en.wikipedia.org/wiki/Mkstemp#Mechanism)
    *outFileStream = (fd < 0) ? nullptr : fdopen(fd, "w+"); // Gets a FILE stream for our newly created file
    if (fd >= 0) {
        *outFileStream = fdopen(fd, "w+"); // Gets a FILE stream for our newly created file
        tempFileMap.insert({ *outFileStream, outFilePath });
    }
    else
        *outFileStream = nullptr;
}

void closeTempFile(FILE* file) {
    std::string filename = tempFileMap[file];
    tempFileMap.erase(file);

    unlink(filename.c_str()); // "unlink" will get the file to be deleted as soon as it's closed: https://stackoverflow.com/a/47033463/9399492
    fclose(file);
}

void __closureRoutine(int sig) {
    exit((sig == SIGINT) ? 130 : 134); // triggers the atexit callback
}

void hookFunctionToClose() {
    signal(SIGABRT, __closureRoutine);
    signal(SIGINT, __closureRoutine);
    atexit(closureRoutine);
}

#else

#error Unsupported system

#endif

// This function is gonna get triggered when the program exists. We use it to clean up our temporary files.
// Do note that it won't be called in the case of forced exits, like by sending SIGTERM on POSIX-compliant systems, or forcefully ending the task in the Task Manager on Windows.
// In such cases, the temporary folder will have to be cleared manually.
// See https://serverfault.com/q/377348 for Unix/Linux and https://superuser.com/a/296827 for Windows
void closureRoutine() {
    // Storing all FILE pointers into a vector beforehand to avoid a "cannot dereference value-initialized map/set iterator" error
    std::vector<FILE*> pairs;
    for (auto it = tempFileMap.begin(); it != tempFileMap.end(); ++it) {
        pairs.push_back(it->first);
    }

    for (FILE*& file : pairs) {
        std::string filename = tempFileMap[file];
        closeTempFile(file);
        printf("Automatically closed & cleaned up %s\n", filename.c_str());
    }
}

int main() {
    // Hook our closure routine to events related to program termination; this will let us clean after ourselves.
    hookFunctionToClose();

    // Get the current temporary directory.
    // On POSIX-based systems, it will often be /tmp.
    // On Windows, it's usually gonna be C:\Users\%USERNAME%\AppData\Local\Temp, or C:\Temp, depending of your priviledges.
    char tempFolderBuff[PATH_MAX] {};
    getTempDir(tempFolderBuff);

    printf("Found temporary folder: %s\n", tempFolderBuff);

    // tempFolderBuff now contains the temporary directory path WITH a trailing directory separator char. This means we only need to append our temporary filename to it.
    char tempFileBuff[PATH_MAX]{};
    FILE* file;
    openTempFileFromFolder(tempFolderBuff, tempFileBuff, &file); // Keeps tempFolderBuff intact and writes the temp filename to tempFileBuff instead, so we can open multiple temporary files easily

    printf("Opened temporary file: %s\n", tempFileBuff);

    if (!file) {
        // Handle the case where we couldn't create a new temporary file
        /* ... */
        exit(1);
    }

    /* Do whatever you want with your file */

    closeTempFile(file);
    printf("Manually closed & cleaned up %s\n", tempFileBuff);

    openTempFileFromFolder(tempFolderBuff, tempFileBuff, &file); // Open another one to test automatic cleanup

    printf("Opened temporary file: %s\n", tempFileBuff);

    if (!file) {
        // Handle the case where we couldn't create a new temporary file
        /* ... */
        exit(1);
    }

    /* Do whatever you want with your file */

    abort(); // Even though we aren't manually closing & cleaning up the file, it should be done automatically.

    return 0;
}
Reasons:
  • Blacklisted phrase (0.5): thanks
  • Blacklisted phrase (1): stackoverflow
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: RedStoneMatt