2012-01-16 3 views
2

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

Она должна быть похожа на эту команду 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 также является опцией, прежде чем я схожу с ума.

Пожалуйста, кому-нибудь дайте мне немного света!

Заранее благодарен!

- Edit -

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

+0

Он падает, потому что вы назначаете его в стек. У вас ограниченный объем памяти в стеке. Вы пытаетесь выделить 1 МБ в стеке, как есть, лучше использовать вызов malloc & memset. –

+0

@ RichardJ.RossIII: Вы не должны 'malloc' &' memset' вообще использовать вместо этого 'calloc'. Таким образом вы можете сэкономить много памяти. –

+0

@ DietrichEpp Действительно? Я слышал наоборот, и этот memset лучше, чем вызов calloc, потому что он больше вложен? Только то, что я слышал, никак не могу сказать. –

ответ

3

Вы можете использовать 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 если что-то пойдет не так.

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

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

+0

Интересно .. Как вы думаете, было бы слишком много работы выполнить действие «убить» по ошибке по умолчанию? – Apollo

+0

Получена ошибка: 'Программный принятый сигнал:« SIGABRT »' и в журнале: '2012-01-16 01: 56: 05.977 Обработчик файлов [2448: 903] *** Завершение приложения из-за неперехваченного исключения« NSInvalidArgumentException », Причина: «Путь к запуску недоступен» *** Стек вызова при первом броске: ' – Apollo

+0

@GiancarloMariot: Как указано, ошибки обнаруживаются при выходе' dd', поэтому нет необходимости убивать 'dd'. Программа 'dd', как правило, выйдет сразу после обнаружения ошибки. –

1

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

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

Что происходит с этим подходом, поскольку 'totalSize' приближается к 10GB? – aroth

+0

Хорошо, тогда вы ввернуты, если по какой-то нечестивой причине у вас больше 10 ГБ ОЗУ. –

+0

@ RichardJ.RossIII: Если вы использовали 'calloc' вместо' malloc'/'memset' (который всегда * должен *), вы можете быть в порядке на 64-разрядных компьютерах, даже если у них не так много ОЗУ. –

4

Это относительно простой в 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), но для тех, которые делают это, один звонок.

+0

Да. Иногда старые способы работают лучше всего. – aroth

+1

Обратите внимание, что не все приложения хотят создавать разреженные файлы. Возможно, вы захотите изменить разрешения на 0666 и позволить пользователю установить свой собственный 'umask'. –

+0

Отлично, я искал что-то, что не выделяет все на память, так что это хорошее начало. Но вы потеряли меня с этим смещением. XD – Apollo

1

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

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

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!"); 
+0

Обратите внимание, что это заставляет зависимость от 'dd' быть доступной и специально расположена в'/bin/dd' - если какое-либо требование не выполняется, ваша программа не будет работать. Хотя это, вероятно, будет работать для вас в течение многих лет, я думаю, что доступны более простые методы, которые не будут иметь этих зависимостей. – sarnold

+0

@sarnold Мне жаль, что я не могу найти хороший метод, как вы говорите, но все они бросают файл в память перед сохранением, и если пользователь запрашивает файл размером 1 ГБ, это, вероятно, приведет к краху приложения или, по крайней мере, станет слоном. Я искал всюду для простого решения, затем я получил здесь и все найденные мной решения, это вы можете увидеть здесь. = ( Если у вас есть новое предложение, было бы здорово! =) – Apollo

+0

[Dietrich's ответ] (http://stackoverflow.com/a/8874811/377270) хорошо; вероятность того, что запуск 'NPROC' setrlimit (2)' ограничения от запуска новых процессов довольно низок, и ссылка на направляющую песочницы, которую он предоставил, указывает мне, что вы, как автор приложения, а не _user_, несут ответственность для обеспечения песочницы, так что это вряд ли станет реальным препятствием для вашего приложения. Мое предложение _does_ создает разреженный файл (который может и не быть тем, чего вы хотите), и он требует работы на другом уровне API. – sarnold

Смежные вопросы