在 ARC 下使用 dispatch_write 将 NSData 写入后台的唯一文件

Using dispatch_write to write NSData to unique file in background under ARC

我正在尝试创建一个具有唯一名称的文件并在后台向其写入数据。

mktempWhenever it is possible, mkstemp() should be used instead, since it does not have the race condition.

使用 mkstemp 会导致打开文件描述符,因此 dispatch_write 似乎很明显。

现在 NSData 必须使用 dispatch_data_create 包裹在 dispatch_data_t 中。必须注意释放需要释放的内存,保留必须保留的内存。在 ARC 下,这不太明显。

+ (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]);
    }
}

所以问题是:

  1. 这有必要吗? mktemp 是否应该与 NSDatawriteToFile:options:error: 一起使用而不用担心 security/race 条件?
  2. 使用空析构函数调用 dispatch_data_create 是否可以避免不必要的复制(保留指向 NSData 缓冲区的指针)?
  3. mkstemps打开的描述符可以和dispatch_write一起使用吗?
  4. 保留对 NSData 的引用是否会使 dispatch_data_t 有效?这是必要的吗? ARC 在这里做什么?
  5. 文件是如何关闭的? dispatch_io_close?

这并不是 dispatch_write(通常 dispatch_data)的真正目的。正如您所发现的,dispatch_data 专注于功能和性能,而不是易用性。而你的问题就这么简单。

另请注意,您正在讨论的竞争条件与正在您的临时目录中快速创建文件的活跃攻击者有关。攻击是这样的:

  • 您是 运行 某些特权用户。 Eve(攻击者)是 运行 非特权用户。
  • 您想在 /tmp 中创建一个您和 Eve 都可以读写的临时文件。
  • 您查看 /tmp 发现某些文件名不存在
  • 在您创建文件之前,Eve 使用您刚刚检查过的名称创建了文件。她使文件世界可写(但它归夏娃所有)。
  • 您现在打开文件并开始写入,但它仍归 Eve 所有。
  • 现在 Eve 可以读取和修改您的数据。这可能会转化为权限提升。

这是对Unix系统的真正攻击。很明显,这不是对 iOS 系统的真正攻击。这并不意味着您不应该使用 mkstemp。你应该。但重要的是要了解您要防范的是什么。这不是 "oops; I collided with myself" 竞争条件,除非您每秒制作数百个文件(不要那样做)。

好的,你是怎么做到的? Matt Gallagher 在 Cocoa with Love: Temporary files and folders in Cocoa 中有一个很好的例子。复制到这里供以后的搜索者使用,但我强烈推荐这篇文章:

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];

现在,最后,您会看到 Matt 创建了文件名和 NSFileHandle。两者都可以使用。您可以使用 NSData 方法写入文件名,也可以使用 NSFileHandle 写入方法。此时不存在使用文件名的竞争条件,因为该文件已经存在并且归您所有。

要在后台写入,只需将其粘贴在 dispatch_async 块中即可。