tags:

views:

582

answers:

3

We want to write to "foo.txt" in a given directory. If "foo.txt" already exists, we want to write to "foo-1.txt", and so on.

There are a few code snippets around that try and answer this question, but none are quite satisfactory. E.g. this solution at CocoaDev uses NSFileManager to test if a path exists to create a safe path. However, this leads to obvious race conditions between obtaining a path and writing to it. It would be safer to attempt atomic writes, and loop the numeric suffix on failure.

Go at it!

+1  A: 

Use the open system call with the O_EXCL and O_CREAT options. If the file doesn't already exist, open will create it, open it, and return the file descriptor to you; if it does exist, open will fail and set errno to EEXIST.

From there, it should be obvious how to construct the loop that tries incrementing filenames until it returns a file descriptor or constructs a filename too long. On the latter point, make sure you check errno when open fails—EEXIST and ENAMETOOLONG are just two of the errors you could encounter.

Peter Hosey
+1  A: 
int fd;
uint32_t counter;
char filename[1024]; // obviously unsafe

sprintf(filename, "foo.txt");
if( (fd = open(filename, O_CREAT | O_EXCL | O_EXLOCK, 0644)) == -1 && errno == EEXIST ) 
{
    for( counter = 1; counter < UINT32_MAX; counter++ ) {
      sprintf(filename, "foo-%u.txt", counter);
      if( (fd = open(filename, O_CREAT | O_EXCL | O_EXLOCK, 0644)) == -1 && errno == EEXIST )
        continue;
      else
        break;
    }
}

if( fd == -1 && counter == UINT32_MAX ) {
 fprintf(stderr, "too many foo-files\n");
} else if( fd == -1 ) {
 fprintf(stderr, "could not open file: %s\n", strerror(errno));
}

// otherwise fd is an open file with an atomically unique name and an
// exclusive lock.
Jason Coco
+1  A: 

How about:

  1. Write the file to a temporary directory where you know there's no risk of collision
  2. Use NSFileManager to move the file to the preferred destination
  3. If step 3 fails due to a file already existing, add/increment a numeric suffix and repeat step 2

You'd be basically re-creating Cocoa's atomic file write handling, but adding in the feature of ensuring a unique filename. A big advantage of this approach is that if the power goes out or your app crashes mid-write, the half-finished file will be tucked away in a tmp folder and deleted by the system; not left for the user to try and work with.

Mike Abdullah