iOS and iCloud: overcoming “bad file descriptor” errors

January 16, 2012 1 Comment by Ben

Pretty quick one today. I beat my head against this for several hours before I found the obvious solution, so I thought I’d jot the info down here for reference, and maybe to help someone else out.

NOTE: There is now a relevant answer on StackOverflow. Unfortunately, this answer wasn’t there when I needed it.

The problem

Recently, while adding iCloud support to the Day One iOS app, I ran into an issue copying files to and from the application’s Documents directory and its corresponding iCloud directory. Day One can run in one of three modes:

  1. Sync entries via iCloud
  2. Sync entries via Dropbox
  3. Don’t sync entries

If you’ve developed for iCloud at all, you know that iCloud documents live within specific, “ubiquitous” directories on the user’s device (discovered via NSFileManager’s¬†URLForUbiquityContainerIdentifier:¬†method). So in order to enable iCloud sync, we needed to copy all files into the appropriate ubiquitous container. Conversely, if the user elects to disable iCloud then we need to copy all files from the ubiquitous container into the application’s Documents directory.

The assumption

We wrote a simple method to copy files from one directory to another, either going into or coming out of iCloud. It uses a basic NSDirectoryEnumerator to find all the files in the current source directory, check if they are present in the destination directory, and copy them there if they aren’t. Here’s a simplified example:

NSURL *sourceDir = [self currentWorkingDir]; // current directory, ie ubiquitous iCloud directory
NSURL *destDir = [self destDirForMovingFromDir:sourceDir]; // where should we copy to?
NSFileManager *fm = [NSFileManager defaultManager];        
NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath:[sourceDir path]];

NSError *mergeError;
while ((sourceFile = [dirEnum nextObject])) {
    NSURL *sourceFileURL = [sourceDir URLByAppendingPathComponent:sourceFile];
    NSURL *destFileURL = [destDir URLByAppendingPathComponent:[sourceFile lastPathComponent]];
    if (![fm fileExistsAtPath:[destFileURL path]]) {
        if (![fm copyItemAtURL:sourceFileURL toURL:destFileURL error:&mergeError]) {
            NSLog(@"ERROR (copy error): %@ -> %@ - %@", sourceFileURL, destFileURL, mergeError);
        }
    }
}

I frequently found “bad file descriptor” errors being logged out. We were assuming that the NSDirectoryEnumerator would only list files that were actually present on the device. As it turns out, that is not the case when enumerating a ubiquitous directory.

The solution

Once you realize that the NSDirectoryEnumerator will list all ubiquitous files that exist on any device, regardless of whether or not they have been downloaded to the current device, the solution becomes pretty obvious:

You can’t copy a file that hasn’t been downloaded.

Adding that check is fairly straight forward. Since our method doesn’t know if we are going into or out of iCloud, we implemented a pretty general solution. Modifying the above example, it looks something like this:

...
while ((sourceFile = [dirEnum nextObject])) {
    ...
    // By default, assume all files are able to be copied.
    NSNumber *ableToBeCopied = [NSNumber numberWithBool:YES];

    // However, ubiquitous items that are not downloaded CANNOT be copied
    if ([fm isUbiquitousItemAtURL:sourceFileURL]) {
        [sourceFileURL getResourceValue:&ableToBeCopied forKey:NSURLUbiquitousItemIsDownloadedKey error:nil];
    }

    // Now copy the file if it doesn't exist and is able to be copied
    if (![fm fileExistsAtPath:[destFileURL path]] && [ableToBeCopied boolValue]) {
        if (![fm copyItemAtURL:sourceFileURL toURL:destFileURL error:&mergeError]) {
            NSLog(@"ERROR (copy error): %@ -> %@ - %@", sourceFileURL, destFileURL, mergeError);
        }
    }
}

Conclusion

See what I mean? Pretty obvious, and I can’t believe I lost so much time tracking this down. If the source file is ubiquitous, it might not be downloaded, and thus you might not be able to copy it. So check it first.

One Comment

  1. Andreas
    5 years ago

    Thanks for sharing! I’ve been looking for a solution to this problem for hours.

Post a Comment

Your email is never published or shared. Required fields are marked *