How to do fsync on an ofstream?
Asked Answered
M

4

31

I want to make sure that an ofstream has been written to the disk device. What's the portable way (portable on POSIX systems) of doing this?

Does that solve the problem if I open the file separately in read-only append mode to get a file descriptor and call fsync with it? Like this:

ofstream out(filename);
/* ...
   write content into out
   ...
*/
out.close();

int fd = open(filename, O_APPEND);
fsync(fd);
close(fd);
Madelainemadeleine answered 24/3, 2009 at 10:19 Comment(0)
C
13

If you're able to use Boost, try a file_descriptor_sink based stream, eg.:

#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <unistd.h>

boost::filesystem::path filePath("some-file");
boost::iostreams::stream<boost::iostreams::file_descriptor_sink> file(filePath);

//  Write some stuff to file.

//  Ensure the buffer's written to the OS ...
file.flush();

//  Tell the OS to sync it with the real device.
//
::fdatasync(file->handle());
Crime answered 23/5, 2014 at 10:11 Comment(2)
Even with the headers I couldn't get this to compile. Has Boost changed since 2014?Tauro
@parsley72, I've just tested it again with Boost 1.80.0; it still works.Crime
L
7

Unfortunately, looking through the standard there is nothing provided by basic_filebuf or any of the basic_[io]?fstream class templates to allow you to extract the underlying OS file descriptor (in the way that fileno() does for C stdio I/O).

Nor is there an open() method or constructor that takes such a file descriptor as a parameter (which would allow you to open the file using a different mechanism and record the filehandle).

There is basic_ostream::flush(), however I suspect that this does not in fact call fsync() -- I expect that, like fflush() in stdio, it only makes sure that the user-space runtime library buffers are flushed, meaning that the OS could still be buffering the data.

So in short there appears to be no way to do this portably. :(

What to do? My suggestion is to subclass basic_filebuf<C, T>:

template <typename charT, typename traits = std::char_traits<charT> >
class my_basic_filebuf : public basic_filebuf<charT, traits> {
    ....

public:
    int fileno() { ... }
    ....
};

typedef my_basic_filebuf<char> my_filebuf;

To use it, you can construct an ofstream using the default constructor, then assign the new buffer with rdbuf():

my_filebuf buf;
buf.open("somefile.txt");

ofstream ofs;
ofs.rdbuf(&buf);

ofs << "Writing to somefile.txt..." << endl;
int fd = static_cast<my_filebuf*>(ofs.rdbuf())->fileno();

Of course you could also derive a new class from basic_ostream to make the process of opening a file and retrieving its file descriptor more convenient.

Lunseth answered 24/3, 2009 at 13:2 Comment(3)
@BЈовић: (1) It does; I'm not sure what your problem is. (2) You commented on a post from 2009 1 hour ago and it already has 1 upvote: that doesn't look like a sockpuppet account at all.Lunseth
I am not sure how my comment disapeared, but your answer is useless. In the most important parts, there are dots. My problem is that your "example" doesn't compile. So, what if it is from 2009? Google pointed me to this QA, and I expected to find it helpfull. But it is not. Also, he is not asking for a portable way to do, but using posix.Japanese
Your comment was probably deleted because it was rude.Lunseth
A
6

std::filebuf probably has a file descriptor somewhere inside it, but getting at it requires horrible implementation-specific hacks.

Here is one such horrible hack for libstdc++.

#include <fstream>
#include <unistd.h>

int GetFileDescriptor(std::filebuf& filebuf)
{
  class my_filebuf : public std::filebuf
  {
  public:
    int handle() { return _M_file.fd(); }
  };

  return static_cast<my_filebuf&>(filebuf).handle();
}

int main()
{
  std::ofstream out("test");

  out << "Hello, world!";
  out.flush();

  fsync(GetFileDescriptor(*out.rdbuf()));
}
Anhydride answered 10/2, 2015 at 18:25 Comment(2)
what is _M_file ?Hans
@AmruthA - A protected member variable of libstdc++'s implementation of std::filebuf.Anhydride
H
2

You cannot portably do fsync() on a file descriptor open for reading, at all. In Linux, fsync() is documented as generating EBADF if the descriptor is not in write mode.

Hospitaler answered 24/3, 2009 at 10:22 Comment(3)
Ok, then what if I open it for appending?Madelainemadeleine
You are not answering the question. The ofstream opens a file for writing, not reading.Japanese
Also, you can fsync() on directory opened in read mode. Actually, that's how you force disk write.Japanese

© 2022 - 2024 — McMap. All rights reserved.