2

I need to open a file for writing. If the file already exists, I don't want to truncate it.

In other words, in plain C I'd do:

int fd = open("output.bin", O_WRONLY | O_CREAT, 0666);
// I don't mind using O_RDWR, btw.

I'm trying to do something similar with GLib's GFile (part of GIO). I first tried:

g_file_create(gfile, G_FILE_CREATE_NONE, NULL, NULL);

But this fails if the file already exists.

I see that there are about 5 other functions that return GFileOutputStream or GFileIOStream, but I don't quite see one that does what I want.

Am I missing something?

Do I need to split this simple task into several small ones? (checking for file existence; if exists, create, otherwise open; all wrapped somehow in a lock.)

(BTW, if it matters: my file will reside on the local filesystem, not a networked one. Also, I'm working in Vala, which is why I don't simply use open() (maybe I could find bindings for it, but I prefer to learn the GIO way of doing things).)

Niccolo M.
  • 3,363
  • 2
  • 22
  • 39
  • 2
    There doesn't seem to be any such function. Perhaps you can check if the return code from `g_file_create` is [`G_IO_ERROR_EXISTS`](https://developer.gnome.org/gio/stable/gio-GIOError.html#G-IO-ERROR-EXISTS:CAPS) and not treat it as failure? – P.P May 21 '18 at 15:39

3 Answers3

3

I ended up doing this in two steps. I used @usr's suggestion of checking for G_IO_ERROR_EXISTS. My code:

public class Downloader {

  public FileIOStream iostream;

  public OutputStream output { get { return iostream.output_stream; } }

  public void create_output_file() throws Error
  {
    File file = File.new_for_path("output.bin");
    try {
      // If file doesn't exist.
      iostream = file.create_readwrite(NONE);
    } catch (Error e) {
      if (e is IOError.EXISTS)
        // It exists.
        iostream = file.open_readwrite();
      else
        throw e;
    }
  }

}

(There could be a race condition here, but it's irrelevant in my case.)

Niccolo M.
  • 3,363
  • 2
  • 22
  • 39
2

The Valadoc.org documentation has good examples so I won't code up a working example here. I think you need GLib.File.new_for_path () and GLib.File.append_to (). The C documentation for g_file_append_to () advises 'Gets an output stream for appending data to the file. If the file doesn't already exist it is created.' Note that append_to () takes a FileCreateFlags argument. There is also an asynchronous version of append_to () for Vala, append_to_async ().

By the way the Vala binding for open () is in the Posix VAPI. See open (), O_WRONLY and O_CREAT at Valadoc.org.

AlThomas
  • 4,169
  • 12
  • 22
  • Thanks, but I can't use `append_to()` because I won't be writing at the end of the file but at some random position within. (And thanks for the Posix pointers: it's nice to see that this binding is effectively built-in.) – Niccolo M. May 21 '18 at 19:58
  • `append_to ()` returns a `FileOutputStream` that has a `seek` method. The `append_to ()` sets the current position to the end of the file that's all, but you can change the position if needs be. – AlThomas May 21 '18 at 20:13
  • 1
    Not so. `append_to()` has the semantic of O_APPEND, whom the man page of `open(2)` describes as: "Before **each** write(2), the file offset is positioned at the end of the file [...] performed as a single atomic step". I've [just verified](https://pastebin.com/yNgWY3tF) that this holds true for Vala's GIO as well. – Niccolo M. May 21 '18 at 22:13
1

The official upstream answer is that GIO, as a high-level abstraction layer, was not originally designed to give guarantees about atomic create-or-open operations (as with O_CREAT), so you have two options:

  • Try creating the file and, if that fails with G_IO_ERROR_EXISTS, try opening it instead; as in your answer.
  • Create/Open the file using open() with O_CREAT (as in the question), and pass the FD to g_unix_output_stream_new() to create a new GOutputStream which wraps the FD and you can write to as if it were returned by g_file_create().

Note that g_unix_output_stream_new() isn’t portable to non-Unix platforms, but that doesn’t appear to be relevant in your case. This second option is the recommended one if you do care about eliminating race conditions.

Note that there are other reasons to avoid the race condition: less code, fewer syscalls (which means better performance), and more readable code (if you ignore the abrupt naming of the open() APIs).

Doing this directly with a method on GFile may be supported in future: there’s an open issue about supporting it in GLib.

Philip Withnall
  • 5,293
  • 14
  • 28