C: создание заголовка файла архива - PullRequest
4 голосов
/ 02 января 2011

Я создаю архиватор / экстрактор файлов (например, tar), используя системные вызовы API POSIX в C. Я выполнил часть бита архивации.

Я хотел бы знать, может ли кто-нибудь помочь мнес некоторым исходным кодом C (используя выше) для создания заголовка файла для файла в C (где header действует как индекс), который описывает атрибуты файлов / метаданные (имя, время и т. д.).Все, что я до сих пор делал, это понимал (не уверен, что это даже правильно), что для создания заголовка файла нужна структура для хранения метаданных, а lseek необходим для поиска начала / конца файла, например:

FileName = file.txt FileSize = 0

FileDir =. / Blah / blah

FilePerms = 000

\ n \ n

Архивирующая часть программы имеет такой процесс:

  1. Получить список всех файлов из командной строки.(Я могу сделать эту часть)
  2. Создать структуру для хранения метаданных о каждом файле: имя (255 символов), размер (64-разрядное целое), дата и время, а также разрешения.
  3. Для каждого файла получите его статистику.
  4. Сохраните статистику каждого файла в массиве структур.
  5. Откройте архив для записи.(Я могу сделать эту часть)
  6. Напишите структуру заголовка.
  7. Для каждого файла добавьте его содержимое в файл архива (в конце / начале каждого файла).
  8. Закройте файл архива.(Я могу сделать эту часть)

У меня проблемы с созданием заголовочного файла в целом, хотя я знаю, что он должен делать, как указано в пронумерованных точках над битами, которые я не могу сделать:заявлено (2,3,4,6,7).

Любая помощь будет оценена.Спасибо.

Ответы [ 2 ]

9 голосов
/ 02 января 2011

Как отмечает ijw , существует несколько способов создать заголовок файла архива.Если кросс-платформенная переносимость вообще будет проблемой - или если вам нужно будет переключаться между 32-битной и 64-битной сборками программного обеспечения на одной и той же платформе - даже тогда, вы должны убедиться, что размеры и компоновкаполя полностью понятны на всех платформах.

Метаданные для каждого файла

Один из способов сделать это - использовать двоичный заголовок фиксированного формата с типами известного размера и порядком байтов.Это то, что предложил ijw.Однако вам нужно будет обрабатывать длинные имена файлов, поэтому вам нужно будет хранить длину (вероятно, в 2-байтовом целом числе без знака), а затем следовать за ней с фактическим путем.

Альтернатива и, как правило,Теперь предпочтительным методом является использование печатных полей (часто называемых форматом ASCII, хотя это неправильно).Время записывается в виде десятичного числа секунд с момента преобразования эпохи в строку и т. Д. Это то, что используют современные архивы ar;это то, что делает GNU tar (более или менее; есть некоторые исторические причуды, которые делают это более запутанным);это то, что делает cpio -c (обычно это по умолчанию в наши дни).Поля могут быть разделены нулями или пробелами;есть простой способ определить конец заголовка;заголовок содержит информацию об имени файла (не обязательно так, как вы бы хотели или ожидаете, но опять-таки, обычно потому, что формат развивался годами), а затем следуют фактические данные.Каким-то образом вы знаете размер каждого поля и файл, который описывает заголовок, чтобы вы могли надежно читать данные.

Эффективность - красная сельдь.Преобразование в / из текстового формата настолько быстрое по сравнению с первым доступом к диску, что практически не возникает проблем с измеримой производительностью.И гарантированная переносимость, как правило, намного перевешивает (микроскопическую) выгоду производительности от использования двоичного формата данных вместо этого - вдвойне, когда двоичные данные должны быть преобразованы на входе или выходе, чтобы получить их в нейтральном для архитектуры формате.

Центральный индекс по сравнению с распределенным индексом

Другой вопрос, который следует рассмотреть, заключается в том, является ли индекс файлов в архиве централизованным (спереди или в конце) или распределенным (метаданные для каждого файла непосредственно предшествуют даннымдля файла).У каждого формата есть некоторые преимущества - как правило, системы используют распределенную версию, потому что вы можете записать информацию для каждого файла, не зная, сколько файлов нужно обработать в общей сложности (например, потому что вы рекурсивно архивируете содержимое каталога).Наличие центрального индекса заранее означает, что вы можете перечислять файлы без чтения всего архива - распределенные метаданные означают, что вы должны прочитать весь файл.Однако центральный индекс усложняет построение архива.

Обратите внимание, что даже при распределенном индексе вам обычно потребуется заголовок для архива в целом, чтобы вы могли определить, что файл имеет форматвы ожидаете.Как правило, существует некоторая информация маркера (!<arch>\n для архива ar, обычно; %PDF-1.2\n в начале файла PDF и т. Д.), Чтобы заверить вас, что файл содержит то, что вы ожидаете.Там могут быть некоторые общие (на уровне архива) метаданные.Затем у вас будут первые метаданные файла, за которыми следуют данные файла, повторяющиеся до конца архива (который может иметь или не иметь формальный маркер конца - больше метаданных).


[H] я бы хотел реализовать его в «двоичном заголовке фиксированного формата», который вы предложили.У меня проблемы с решением, какие команды / функции необходимы.

Я намеревался предложить вам не использовать двоичный заголовок фиксированного формата;Вы должны использовать текстовый формат заголовка.Если вы можете понять, как сделать двоичный формат, будьте моим гостем (я делал это много раз за эти годы - это не значит, что я думаю, что это хорошая идея).

Итак, здесь есть некоторые указатели на формат «текстовый заголовок».

Для метаданных файла вы можете указать, что вы включаете:

  • размер
  • режим (права доступа, тип)
  • владелец
  • группа
  • время модификации
  • длина имени
  • имя

Вы можете разумно решить, что размеры вашего файла ограничены 64-разрядными целыми числами без знака, что означает 20 десятичных цифр.Режим может быть напечатан как 16-разрядное восьмеричное число, требующее 6 восьмеричных цифр.Владелец и группа могут быть напечатаны в виде значений UID и GID (а не имени), и в этом случае вы можете использовать 10 цифр для каждого.В качестве альтернативы, вы можете решить использовать имена, но вам следует разрешить имена до 32 символов каждый.Обратите внимание, что имена обычно более переносимы, чем числа.Ни имя, ни номер не имеют большого значения на принимающем компьютере, если вы не извлекаете данные как root (но зачем вам это нужно?).Время модификации - это 32-разрядное целое число со знаком, представляющее количество секунд с начала эпохи (1970-01-01 00: 00: 00Z).Вы должны учесть ошибку Y2038, увеличив количество секунд, превышающее 32-битное значение;вы можете решить, что 12 старших цифр выведут вас из кризиса Y10K (примерно в 4 раза), и это достаточно хорошо;Вы могли бы решить, чтобы позволить на доли секунды тоже.Вместе это говорит о том, что 26 пробелов для метки времени должны быть излишними.Вы можете решить, что каждое поле будет отделено от следующего пробелом (для ясности подумайте «легкость отладки»!).Вы можете разумно решить, что все имена файлов будут ограничены 4 десятичными цифрами общей длины.

Вам необходимо знать, как форматировать типы переносимо - #include <inttypes.h> ваш друг.

Вызатем разработайте строку формата для печати (записи) метаданных файла и параллельную строку для сканирования (чтения) метаданных файла.

Печать:

"%20" PRIu64 " %06o %-.32s %-.32s %26" PRIu64 " %-4d %s\n"

Это также печатает имя.Завершает заголовок новой строкой.Общий размер составляет 127 байт плюс длина имени файла.Это, вероятно, чрезмерно, но вы можете настроить числа под себя.

Сканирование:

"%" SCNu64 " %o %.32s %.32s %" SCNu64 "%d"

Это не сканирует имя;вам нужно тщательно создать сканер для имени, не в последнюю очередь потому, что вам нужно прочитать пробелы в имени.Фактически, код для сканирования имени пользователя и имени группы также не предполагает пробелов.Если это неприемлемо (то есть имена могут содержать пробелы), то для обработки входных данных вам необходим более сложный формат сканирования или что-то отличное от sscanf().

Я предполагаю, что 64-битовое целое для поля времени, вместо того, чтобы смешивать дробные секунды и т. д., даже несмотря на то, что места достаточно, чтобы выделить доли секунды.Скорее всего, вы сэкономите здесь немного места.

4 голосов
/ 02 января 2011

Получение информации для каждого файла, которое вы можете сделать с помощью системного вызова stat ().

Для написания заголовка, вот два решения.

Тривиально, но зло:

struct file_header {
... data you want to put in 
} fhdr;

fwrite(file, fhdr, sizeof(fhdr));

Это зло, потому что структура структуры варьируется от машины к машине, как и порядок байтов и размер основных типов, таких как 'int'. Файл, написанный вашей программой, может быть недоступен для чтения вашей программой, когда он компилируется на другом компьютере, или даже в некоторых случаях с другим компилятором на том же компьютере.

Нетривиально, но безопасно:

char name[xxx];
uint32_t length; /* Fixed byte length across architectures */
...

fwrite(file, name, sizeof(name));
length=htonl(length); /* Or something else that converts 
                         the length to a known endianness */
fwrite(file, &length, sizeof(length);

Лично я не фанат htonl () и друзей, я предпочитаю писать что-то, что преобразует uint32_t в uchar [4] с использованием операторов сдвига (которые можно написать тривиально с помощью операторов сдвига), потому что C Зафиксируйте формат даже целого числа в памяти. На практике вам будет трудно найти то, что не хранит uint32_t как 4 байта по 8 бит, но это нужно учитывать.

Перечисленные выше переменные могут быть элементами структуры в вашей структуре. Обратный процесс при чтении оставлен читателю в качестве упражнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...