open with O_CREAT - was it opened or created?

37,889

Solution 1

Based roughly on your comments, you want something along the lines of this function:

/* return the fd or negative on error (check errno);
   how is 1 if created, or 0 if opened */
int create_or_open (const char *path, int create_flags, int open_flags,
                    int *how) {
    int fd;
    create_flags |= (O_CREAT|O_EXCL);
    open_flags &= ~(O_CREAT|O_EXCL);
    for (;;) {
        *how = 1;
        fd = open(path, create_flags);
        if (fd >= 0) break;
        if (errno != EEXIST) break;
        *how = 0;
        fd = open(path, open_flags);
        if (fd >= 0) break;
        if (errno != ENOENT) break;
    }
    return fd;
}

This solution is not bullet proof. There may be cases (symbolic links maybe?) that would cause it to loop forever. Also, it may live-lock in certain concurrency scenarios. I'll leave resolving such issues as an exercise. :-)


In your edited question, you pose:

I have 10 processes which try open the same file more or less at the same time using open(O_CREAT) call, then delete it.

A hack-ish, but more bullet proof, solution would be to give each process a different user ID. Then, just use the regular open(path, O_CREAT|...) call. You can then query the file with fstat() on the file descriptor, and check the st_uid field of the stat structure. If the field equals the processes' user ID, then it was the creator. Otherwise, it was an opener. This works since each process deletes the file after opening.

Solution 2

Use O_EXCL flag with O_CREAT. This will fail if the file exists and errno will be set to EEXIST. If it does fail then attempt open again without O_CREAT and without O_EXCL modes.

e.g.

int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
if ((fd == -1) && (EEXIST == errno))
{
    /* open the existing file with write flag */
    fd = open(path, O_WRONLY);
}
Share:
37,889
Sergey
Author by

Sergey

Updated on February 01, 2020

Comments

  • Sergey
    Sergey over 4 years

    I have 10 processes which try open the same file more or less at the same time using open(O_CREAT) call, then delete it. Is there any robust way to find out which process actually did create the file and which did open already create file, for instance, if I want to accurately count how many times that file was opened in such scenario.

    I guess I could put a global mutex on file open operation, and do a sequence of open() calls using O_CREAT and O_EXCL flags, but that doesn't fit my definition of "robust".

  • Sergey
    Sergey about 11 years
    can you please clarify "attempt to open again without O_CREAT". If I omit O_CREAT from your code, I'll be left with O_EXCL only, and accorting to this "In general, the behavior of O_EXCL is undefined if it is used without O_CREAT".
  • Jonathan Leffler
    Jonathan Leffler about 11 years
    If the O_EXCL create fails because EEXISTS, try again without the O_EXCL and without the O_CREAT! Note that when you have O_CREAT, open() takes 3 arguments; the third is the file permissions mode (0644 or similar). You also need one of O_RDONLY, O_WRONLY or O_RDWR (though omitting them is equivalent to O_RDONLY, but creating a file in read-only mode is modestly pointless).
  • suspectus
    suspectus about 11 years
    I've amended the post which hopefully will clarify.
  • Sergey
    Sergey about 11 years
    ok, but while the processor is executing the comments right after the if-check, and someone else opens my file, what do I do then ?
  • jxh
    jxh about 11 years
    @Sergey: Then your problem description is not complete, so we can't answer it.
  • Jonathan Leffler
    Jonathan Leffler about 11 years
    @Sergey? What's your goal? Multiple processes can have the same file open. If you use O_APPEND, you can get atomic writes at the end. Why do you care whether it existed or not that isn't handled by the options to open()?
  • Sergey
    Sergey about 11 years
    Ok, please help me create a complete description. I've marked the important part with bold, what else is missing ?
  • jxh
    jxh about 11 years
    @Sergey: We don't know what problem you actually encountered. We don't know what problem you are trying to solve. We don't know what your code is trying to do. Only you can tell us that.
  • suspectus
    suspectus about 11 years
    @Sergey you will need to check that the second open() is successful and handle open failure appropriately.
  • Sergey
    Sergey about 11 years
    @JonathanLeffler, I have 10 processes trying to open the file, which might no exist yes. I want to know, if it is the case, which one process did create the new file, and which other 9 did open existing file.
  • jxh
    jxh about 11 years
    @Sergey: My suggestion is that you attempt to solve that problem with the answer suspectus provided. The process that succeeds with the first open knows who it is. The other nine will fail on the first open. You make code to do the rest. If there is an issue you cannot resolve on your own, bring it back in a separate question, showing the code, describe what it was supposed to do, and what it does instead.
  • Sergey
    Sergey about 11 years
    solution posted by @suspectus kinda works, but my concern is that it is not 100% safe. File can be deleted in-between 2 lines and then 2nd open will fail. I guess I need to mention that in original question. To make this solution be bulletproof, a retry loop needs to be introduced to guard the code against deletions.
  • Sergey
    Sergey about 11 years
    To fight live-lock I'd fallback to some sort of global shared mutex after a few iterations. But then the solution becomes something I was hoping to avoid.
  • Sergey
    Sergey about 11 years
    I accept this answer since this is the closest to what I wanted to achieve.
  • Admin
    Admin almost 5 years
    In this case, but also in general, consider compiling your programs with _FORTIFY_SOURCE set to 1 which adds some checks at compile-time only.