Сохранить пустой файл в Objective-C

Я работаю над кодом, который, помимо прочего, должен сохранять файл с нулевым заполнением. Можно ожидать создания файла размером от 10 МБ до даже 1 ГБ пустым.

Это должно быть похоже на эту команду Unix:

dd if=/dev/zero of=my_image.dsk count=20480

Я мог бы сделать эту работу с небольшими размерами:

int totalSize = 1024*1024;
char buf[totalSize];
strcpy(buf,"");

NSData *theData = [NSData dataWithBytes:buf length:sizeof(buf)];

//NSLog(@"%@", theData);
NSLog(@"%lu", sizeof(buf));

[theData writeToFile:@"/Users/foo/Desktop/my_image.dsk" atomically:YES];

Но если я попробую большее значение (например, 1024*1024*10), произойдет сбой. Итак, я попробовал:

NSFileManager *ddd = [[NSFileManager alloc ] init];
[ddd createFileAtPath:@"/Users/foo/Desktop/my_image.dsk" contents:[NSData data] attributes:nil];

Это создает пустой файл, но это не хорошо, потому что размер файла равен нулю. Это не должно быть ноль.

Я часами пытался найти ответ без успеха. Я хочу сделать это в Obj-C, но C также вариант, прежде чем я сойду с ума.

Пожалуйста, дайте мне немного света!

Заранее спасибо!

-- Редактировать --

Спасибо всем, но еще одно: можно ли писать, не выделяя все на память?

4 ответа

Решение

Ты можешь использовать dd, Это позволит вам писать, например, 10 ГБ, не беспокоясь о памяти или адресном пространстве.

NSTask *task = [NSTask new];
long size = ...; // Note! dd multiplies this by 512 by default
NSString *path = ...;
[task setLaunchPath:@"/bin/dd"];
[task setArguments:[NSArray arrayWithObjects:@"dd", @"if=/dev/zero",
    [NSString stringWithFormat:@"of=%s", [path fileSystemRepresentation]],
    [NSString stringWithFormat:@"count=%ld", size],
    nil]];
[task launch];
[task waitUntilExit];
if ([task terminationStatus] != 0) {
    // an error occurred...
}
[task release];

Преимущество в том, что искушенные пользователи могут убивать dd если что-то пойдет не так.

О песочнице: я подозреваю, что это будет хорошо работать даже в песочнице, но мне было бы интересно узнать наверняка. Согласно документации ( ссылка), подпроцесс "просто наследует песочницу процесса, который его создал". Это именно то, как вы ожидаете, что это будет работать на Unix.

Вы можете быть уверены, что dd будет держаться, так как Apple утверждает, что OS X соответствует SUSv3.

Это относительно просто в API POSIX:

int f = open("filename", O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
if (f < 0) {
    perror("open filename");
    exit(1);
}

char empty = 0;

pwrite(f, &empty, sizeof(char), <offset into the file>);

Смещение указывается с использованием целого числа типа off_t, который может быть недостаточно большим для ваших конкретных потребностей. Обратитесь к документации по системному API, чтобы обнаружить 64-битные файловые интерфейсы, если они недостаточно велики.

Лучшая часть этого подхода состоит в том, что он занимает всего несколько байтов памяти программы - вы не выделяете 10 гигабайт ничего для записи вашего файла.

Еще один более простой подход заключается в использовании truncate(2) системный вызов. Не все платформы поддерживают расширение файла с помощью truncate(2), но для тех, кто это делает, это один звонок.

Попробуйте этот код:

int totalSize = 1024*1024;
// don't allocate on the stack
char *buf = calloc(totalSize, sizeof(char));

NSData *theData = [NSData dataWithBytesNoCopy:buf length:totalSize];

//NSLog(@"%@", theData);
NSLog(@"%lu", totalSize);

[theData writeToFile:@"/Users/foo/Desktop/my_image.dsk" atomically:YES];

Вы могли бы также попробовать это, меньше памяти, и это работает с файлом 1 ГБ.

void writeBlankBytes(FILE *file, size_t numKB)
{
    static char *oneKBZero = NULL;

    if (oneKBZero == NULL)
    {
        oneKBZero = calloc(1024, sizeof(char));
    }

    for (int i = 0; i < numKB; i++) {
        fwrite(oneKBZero, 1, 1024, file);
    }
}

Спасибо всем. Я пришел к решению. Это в основном набросок кода, который я собираюсь добавить в приложение какао.

Работает нормально, и я думаю, что мне нужно только некоторые меры предосторожности с переменной размера, и этого будет достаточно.

long size = 2000*500;

NSString *path = [[NSString alloc] initWithFormat: @"/Users/foo/Desktop/testeFile.dsk"];

NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/dd"];
[task setArguments:[NSArray arrayWithObjects:
                    @"if=/dev/zero",
                    [NSString stringWithFormat:@"of=%s", [path fileSystemRepresentation]],
                    [NSString stringWithFormat:@"count=%ld", size],
                    nil]];
NSLog(@"%@",[task arguments]);
[task launch];

//[task waitUntilExit]; //The app would stuck here.. better use the loop

int cont = 0;
while ([task isRunning]) {
    cont++;
    if(cont%100==0) NSLog(@". %d", cont);
}

if ([task terminationStatus] != 0) {
    NSLog(@"an error occurred...");
}
[task release];
[path release];

NSLog(@"Done!");
Другие вопросы по тегам