Thursday, February 3, 2022

[SOLVED] Gnuplot and std::filesystem::remove

Issue

I'm trying to use gnuplot from C++ application, gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04). I've encountered strange behavior concerning plotting to files.

So, the reproducible example is:

#include <iostream>
#include <filesystem>

int main()
{
    // whatever valid filename
    std::string name1 = "/tmp/1.png";
    // open gnuplot pipe
    auto gp = popen("gnuplot", "w");
    // plot sin(x) to file. Note "unset output" in the end.
    std::string cmd="set term png\nset output '"+name1+"'\nplot sin(x)\nunset output\n";
    // send the command to gnuplot
    fwrite(cmd.c_str(), sizeof(char), cmd.length(), gp);
    std::error_code ec;
    // removing the file
    if (!std::filesystem::remove(name1, ec))
        std::cout<<"unsuccesfully: "<<ec.value()<<"\s"<<ec.message()<<"\n";
    pclose(gp);
    return 0;
}

The output is (very strange):

unsuccesfully: 0 Success

What happens: gnuplot successfully writes a valid png file to desired destination. However, std::filesystem::remove does not remove the file, returns false and (therefore) prints cryptic message about success with error code 0. Moving pclose(gp); line before std::filesystem::remove solves the problem, so it does look like gnuplot holds the file. What is also strange that if I do the same manually, I mean, I launch gnuplot, issue the same command, and not exit, I'm able to remove the file with unlink /tmp/1.png. I'm aware about gnuplot's set output or unset output requirement, and tried both variants.

Why the std::filesystem::remove acts this strange?


Solution

Why the std::filesystem::remove acts this strange?

You seem to misunderstand the return value and the error value (ec in your code) of std::filesyste::remove(). The function does not raise an error even if there is no file you are trying to remove. The function returns false if there is no file you are trying to remove; otherwise, it returns true. See the document of 'std::filesystem::remove()' in the draft of C++17.

Effects: If exists(symlink_­status(p, ec)), the file p is removed as if by POSIX remove().

Returns: false if p did not exist, otherwise true. The signature with argument ec returns false if an error occurs.

Since no error is raised just because there are no files to be removed, ec.value() in your code will return 0, indicating successful completion.

It's a bit like the behavior of the UNIX command 'rm -f'.

You can check the behavior of std::filesyste::remove() with inserting the following into your code.

    std::error_code ec;
    int retval = std::filesystem::remove(name1, ec);
    if ( ! ec ) { // Success
      std::cout<<"succesfully: \n";
      if ( retval ) {
        std::cout<<"file existed and removed\n";  
      }
      else {
        std::cout<<"file didn't exist\n";
      }
    } 
    else {        // No error
      std::cout<<"unsuccesfully: "<<ec.value()<<" "<<ec.message()<<"\n";
    }

Addition

The reason why the position of pclose() changes the result is that the stream opened by popen() is buffered.

When std::filesystem::remove() is called, the command written by fwrite() is not yet received by gnuplot because of the buffering. Therefore, in this step, the file "/tmp/1.png" has not been created yet.

Then, when pclose() is called, gnuplot receives the commands, and the file "/tmp/1.png" is created by gnuplot. The file that you looked at was the one created after calling std::filesystem::remove().

You can flush the buffer explicitly using the function fflush(). However, even if you use fflush(), there is still a possibility that std::filesystem::remove() will be called before the gnuplot command has finished due to the asynchronous nature of popen.

To ensure that the file is erased after gnuplot process finished, you will need the implementation (or wrapper libraries) that gnuplot and c++ program can be synchronized.



Answered By - binzo
Answer Checked By - David Marino (WPSolving Volunteer)