Detecting errors with OutputStreams

An issue that has annoyed me quite often in the past is that PropertiesFile does not check if there is enough room on the hard drive when saving its content. When that happens, instead of failing to update the preferences file, the PropertiesFile::save() just replaces my current, working, preferences file with an empty one.

More generally, I think the FileOutputStream is a bit lacking on the error reporting side. The only way I could figure to detect that there was an error writing the file is to call FileOutputStream::flushBuffer() (but this function is private) and check the return value.

Another issue that happens on Linux is that application settings are not synced to the disk when they are saved. In the case of a brutal shutdown ( like computer unplugged, or virtualbox vm being shutdown ) , that will also cause the loss of the PropertiesFile content if it had been saved some time before. It is surprisingly easy to reproduce:

 - install ubuntu 15.10 on a virtual box vm

 - run the Introjucer, close it, notice that a file has been created in ~/.config/Introjucer/Introjucer.settings

- kill the virtual vm (close the vm window, select "shut down computer")

- start ubuntu again, and notice that your ~/.config/Introjucer/Introjucer.settings is still there , but is now empty. All the prefs are gone

 

Here is a (quite ugly) change I made to XmlElement::writeToFile ( which is called by PropertiesFile::saveAsXml ) in order to detect the disk full

situation , and also in order to trigger a fsync() that will actually write the file to disk and avoid loosing its content if it has been updated before a hard reboot

bool XmlElement::writeToFile (const File& file,
                              StringRef dtdToUse,
                              StringRef encodingType,
                              const int lineWrapLength) const
{
    TemporaryFile tempFile (file);

    {
        FileOutputStream out (tempFile.getFile());

        if (! out.openedOk())
            return false;


        writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength);
        bool ok = out.flushBuffer(); // ok is false => the disk is full
        out.flush(); // on linux, will trigger a call to fsync()
        if (!ok) return false; // do NOT overwrite the target file if there was an error
    }

    return tempFile.overwriteTargetFileWithTemporary();
}

(these kind of issues with file content being lost in case of hard reboots or crashes on linux where discussed a lot a few years ago : http://www.microhowto.info/howto/atomically_rewrite_the_content_of_a_file.html

https://linux.slashdot.org/story/09/03/11/2031231/apps-that-rely-on-ext3s-commit-interval-may-lose-data-in-ext4 )