3

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?

Suthiro
  • 1,210
  • 1
  • 7
  • 18

1 Answers1

1

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

You seem to misunderstand the return value and the error code (ec in your code) of std::filesystem::remove(). The function does not raise an error if the file you are trying to remove does not exist (ec will be zero). Only the function without error_code& returns false if the file you are trying to remove doesn't exist and true when it does. See the description of std::filesystem::remove() on cppreference.com.

Effects: the file or empty directory identified by the path p is deleted as if by the POSIX remove. Symlinks are not followed (symlink is removed, not its target).

Returns: true if the file was deleted, false if it did not exist. The overload that takes error_code& argument returns false on errors.

Since no error is raised, because there are just 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<<"successful: \n";
      if ( retval ) {
        std::cout<<"file existed and removed\n";  
      }
      else {
        std::cout<<"file didn't exist\n";
      }
    } 
    else {        // Error
      std::cout<<"unsuccessful: "<<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.

Carlo Wood
  • 5,648
  • 2
  • 35
  • 47
binzo
  • 1,527
  • 1
  • 3
  • 13
  • I think I understood it right. According to [docs](https://en.cppreference.com/w/cpp/filesystem/remove): "**Return value**: ```true``` if the file was deleted, ```false``` if it did not exist. The overload that takes ```error_code&``` argument returns ```false``` on errors." I use overloaded variant, it returns ```false```, and the file definitely exists. Therefore, the ```error_code&``` must be set, but it does not. If the function thinks there is no such file then I don't understand why. Also, why the position of the ```pclose(gp);``` line matters? – Suthiro Nov 21 '20 at 08:20
  • The reason why the position of pclose() changes the result is that the stream opened by popen() is buffered. I added the explanation to the answer. – binzo Nov 21 '20 at 13:04
  • "Therefore, the error_code& must be set ...". I don't think so because the specification says the behavior of doing nothing if there are no files to be deleted. – binzo Nov 21 '20 at 13:10
  • Thank you very much for the explanation! I will check it on Monday if the file is created before ```pclose``` or not, using breakpoint and/or ```std::cin```. – Suthiro Nov 21 '20 at 17:12
  • You're right: adding ```fflush(gp); std::cin.get();``` after the ```fwrite``` line solves the issue. Without ```std::cin.get();``` there is racing condition and the result differs from run to run (```std::filesystem::remove()``` almost always "fails" though). Thanks again for the detailed explanation! – Suthiro Nov 23 '20 at 02:47