I am attempting to create a file with a unique name and write data to it in the background.
mktemp
says Whenever it is possible, mkstemp() should be used instead, since it does not have the race condition.
Using mkstemp
results in an open file descriptor, so dispatch_write
seems to be obvious.
Now NSData
must be wrapped in a dispatch_data_t
using dispatch_data_create
. Care must be taken to free memory that needs to be freed, and retain memory that must be retained. Under ARC, this is less than obvious.
+ (void) createUnique:(NSData*)content name:(NSString*)name
extension:(NSString*)extension
completion:(void (^)(NSURL* url, NSError* error))completion {
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_data_t data = dispatch_data_create(content.bytes, content.length,
queue, ^{});
// dispatch_data_create() copies the buffer if DISPATCH_DATA_DESTRUCTOR_DEFAULT
// (= NULL) is specified, and attempts to free the buffer if
// DISPATCH_DATA_DESTRUCTOR_FREE is specified, so an empty destructor is
// specified.
dispatch_fd_t descriptor;
// Ignore details of creating the template C string
strcpy(nameCString, templateCString);
descriptor = mkstemps(nameCString, extensionLength);
free(nameCString);
if (descriptor != -1) {
dispatch_write(descriptor, data, queue,
^(dispatch_data_t data, int error) {
NSData* strongContent = content;
// Will this keep the NSData reference until the
// write is finished?
if (error) {
completion(nil, [NSError
errorWithDomain:NSURLErrorDomain
code:error userInfo:nil]);
} else {
// Ignore details of getting path from nameCString.
completion([NSURL URLWithString:path], nil);
}
// How does the file get closed?
});
} else {
completion(nil, [NSError errorWithDomain:NSURLErrorDomain code:errno
userInfo:nil]);
}
}
So the questions are:
mktemp
be used with NSData
's writeToFile:options:error:
and not worry about security/race conditions?dispatch_data_create
OK with the empty destructor to avoid unnecessary copying (keep a pointer to the NSData
buffer)?mkstemps
be used with dispatch_write
?NSData
keep the dispatch_data_t
valid? Is this necessary? What is ARC doing here?dispatch_io_close
?This isn't really what dispatch_write
(and dispatch_data
in general) is for. As you've discovered, dispatch_data
is focused on power and performance, not ease of use. And you have such a simple problem.
Also note that the race condition you're discussing has to do with an active attacker who is rapidly creating files in your temp directory. The attack goes like this:
/tmp
, which both you and Eve can read and write./tmp
and see that some filename does not existThis is a real attack on Unix systems. It should be obvious that it is not a real attack on iOS systems. That doesn't mean you shouldn't use mkstemp
. You should. But it's important to understand what you're protecting against. This isn't a "oops; I collided with myself" race condition unless you're making hundreds of files a second (don't do that).
OK, so how do you do it? Matt Gallagher has a great example in Cocoa with Love: Temporary files and folders in Cocoa. Copying here for future searchers, but I highly recommend the article:
NSString *tempFileTemplate =
[NSTemporaryDirectory() stringByAppendingPathComponent:@"myapptempfile.XXXXXX"];
const char *tempFileTemplateCString =
[tempFileTemplate fileSystemRepresentation];
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
strcpy(tempFileNameCString, tempFileTemplateCString);
int fileDescriptor = mkstemp(tempFileNameCString);
if (fileDescriptor == -1)
{
// handle file creation failure
}
// This is the file name if you need to access the file by name, otherwise you can remove
// this line.
tempFileName =
[[NSFileManager defaultManager]
stringWithFileSystemRepresentation:tempFileNameCString
length:strlen(tempFileNameCString)];
free(tempFileNameCString);
tempFileHandle =
[[NSFileHandle alloc]
initWithFileDescriptor:fileDescriptor
closeOnDealloc:NO];
Now, at the end of this, you see that Matt has created both a filename and an NSFileHandle
. Either is fine to use. You can use NSData
methods to write to the filename, or you can use NSFileHandle
write methods. There is no race condition at this point to use the file name, because the file already exists and is owned by you.
To write this in the background, just stick it in a dispatch_async
block.