简体   繁体   中英

Safe use of std::tmpnam

so I am developing an application, which requires to create a file, write into it, call another program with that file is an input, and delete the file.

I looked for possible solution and one solution looks like this.

std::FILE* tmpf = std::tmpfile();
std::fputs("Hello, world", tmpf);

According to the documentation of std::tmpfile , if either the file is closed manually, or the program exits in natural way, the file will be deleted. This looks like good solution with one exception. It looks messy (using CI/O, instead of C++ streams).

Another solution would be to use std::tmpnam , which will generate unique file name.

std::string file_name = std::tmpname(NULL);
// (*)
std::fstream stream{file_name};
stream << "Hello World" << std::endl;

But there is a problem with this one too. If another program creates file with the same name, while my program is in (*) , we will both be doing operations on same file, which is certainly something I'd like to avoid.

C++ STL (still) does not support file system operations, like checking if file exists. I could use something like stat to check that, but since checking for file and creating it is not atomic, it would not solve anything. And I did not find C++ STL method of atomic operation, which would: Check if file exists, if not, open it, if yes, fail.

So, my question is, what is the right way of solving this problem? Did I miss something?

Here is my almost not messy solution:

#include <cstdio>
#include <iostream>
#include <string>
#include <ext/stdio_filebuf.h> // libstdc++ specific

int main()
{
    __gnu_cxx::stdio_filebuf<char> tmpfile_buf{std::tmpfile(), std::ios::in | std::ios::out | std::ios::binary};
    std::iostream tmpstream{&tmpfile_buf};

    // write in stream
    tmpstream << "Hello World" << std::endl;
    tmpstream.seekg(0);

    // read from stream
    std::string str;
    std::getline(tmpstream, str);
    std::cout << str << std::endl;
}

Live example

std::tmpfile() is used and a stream is build from a buffer having an underlying FILE* . This is a GNU extension and thus not portable :(.

Your specification "...create a file, write into it, call another program with that file is an input, and delete the file." is incompatible with tmpfile(). For one thing, there is no (portable) way to get the file name from the FILE pointer you get from tmpfile(), secondly on POSIX platforms tmpfile() will typically delete the file from the directory before returning from tmpfile() (if you're unfamiliar with POSIX filesystem semantics, the file exists as long as your process has an open file descriptor to it even after it's deleted from the directory, meaning there is no way to access it from the filesystem).

Given that, you'll have to use some kind of tmpname() type approach, and figure out a way to prevent two processes from simultaneously accessing it (file locks, check link count with stat(), or such).

Or better yet, don't use files for interprocess communication. For the simplest possible(?) case, create a pipe in the parent, connect it to stdin of the child.

I may have a Boost solution to suggest, but I can't test it here.

With a FILE* returned from std::tmpfile() , one can get a file descriptor:

#include <cstdio>
FILE* const tmpfile = std::tmpfile();
const int fd = ::fileno(tmpfile);

and at this stage, everything looks good to use boost::iostreams::file_descriptor_source :

#include <boost/iostreams/code_converter.hpp>
#include <boost/iostreams/maped_file.hpp>
file_descriptor_source tmpstream(fd, boost::iostreams::close_handle);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM