Как разобрать tar файл в С++

Что я хочу сделать, это загрузить файл .tar с несколькими каталогами по 2 файла. Проблема в том, что я не могу найти способ прочитать tar файл без фактического извлечения файлов (используя tar).

Идеальное решение будет выглядеть примерно так:

#include <easytar>

Tarfile tar("somefile.tar");
std::string currentFile, currentFileName;
for(int i=0; i<tar.size(); i++){
  file = tar.getFileText(i);
  currentFileName = tar.getFileName(i);
  // do stuff with it
}

Мне, вероятно, придется написать это сам, но любые идеи будут оценены.

Ответы

Ответ 1

Я подумал об этом после небольшой работы. tar file spec фактически сообщает вам все, что вам нужно знать.

Во-первых, каждый файл начинается с 512-байтного заголовка, поэтому вы можете представить его с помощью char [512] или char *, указывающего где-то в вашем более крупном массиве char (если у вас есть весь файл, загруженный в один массив, например).

Заголовок выглядит следующим образом:

location  size  field
0         100   File name
100       8     File mode
108       8     Owner numeric user ID
116       8     Group numeric user ID
124       12    File size in bytes
136       12    Last modification time in numeric Unix time format
148       8     Checksum for header block
156       1     Link indicator (file type)
157       100   Name of linked file

Итак, если вы хотите получить имя файла, вы можете получить его прямо здесь с помощью string filename(buffer[0], 100);. Имя файла заполнено нулями, поэтому вы можете сделать проверку, чтобы убедиться, что там хотя бы один нуль, а затем оставьте размер, если вы хотите сэкономить место.

Теперь мы хотим знать, является ли это файлом или папкой. Поле "индикатор ссылки" содержит эту информацию, поэтому:

// Note that we're comparing to ascii numbers, not ints
switch(buffer[156]){
    case '0': // intentionally dropping through
    case '\0':
        // normal file
        break;
    case '1':
        // hard link
        break;
    case '2':
        // symbolic link
        break;
    case '3':
        // device file/special file
        break;
    case '4':
        // block device
        break;
    case '5':
        // directory
        break;
    case '6':
        // named pipe
        break;
}

На этом этапе у нас уже есть вся информация, которая нам нужна о каталогах, но нам нужна еще одна вещь из обычных файлов: фактическое содержимое файла.

Длина файла может быть сохранена двумя различными способами: либо в виде восьмеричной строки с нулевым нулевым символом, либо с нулевым завершением, либо с помощью кодирования base-256, обозначаемого установкой старшего бита крайний левый байт числового поля ".

Числовые значения кодируются в восьмеричных числах с использованием цифр ASCII с начальными нулями. По историческим причинам следует использовать окончательный символ NUL или пробел. Таким образом, хотя для сохранения размера файла предусмотрено 12 байтов, можно сохранить только 11 восьмеричных цифр. Это дает максимальный размер файла 8 гигабайт в архивных файлах. Чтобы преодолеть это ограничение, звезда в 2001 году ввела кодировку base-256, которая указана путем установки старшего бита самого левого байта числового поля. GNU-tar и BSD-tar следовали этой идее. Кроме того, версии tar до первого стандарта POSIX с 1988 года заполняют значения пробелами вместо нулей.

Вот как бы вы читали восьмеричный формат, но я не написал код для версии base-256:

// in one function
int size_of_file = octal_string_to_int(&buffer[124], 11);

// elsewhere
int octal_string_to_int(char *current_char, unsigned int size){
    unsigned int output = 0;
    while(size > 0){
        output = output * 8 + *current_char - '0';
        current_char++;
        size--;
    }
    return output;
}

Итак, теперь у нас есть все, кроме фактического содержимого файла. Все, что нам нужно сделать, это захватить следующие size байты данных из файла tar, и у нас будет наш файл:

// Get to the next block after the header ends
location += 512;
file_contents = new char[size];
memcpy(file_contents, &buffer[location], size);
// Go to the next block by rounding up to 512
// This isn't necessarily the most efficient way to do this,
// but it the most obvious.
location += (int)ceil(size / 512.0)

Ответ 2

Вы посмотрели libtar?

Из информации пакета fink:

libtar-1.2-1: API обработки файлов Tar  libtar - это библиотека C для управления файлами tar POSIX. Он обрабатывает  добавление и извлечение файлов в/из архива tar.  libtar предлагает следующие функции:
 * Гибкий API - вы можете манипулировать отдельными файлами или просто  извлечь весь архив сразу.
 * Позволяет пользовательские функции read() и write(), такие как  zlib gzread() и gzwrite().
 * Поддерживает форматы файлов POSIX 1003.1-1990 и GNU tar.

Не С++ как таковой, но вы можете легко ссылаться на c...

Ответ 3

libarchive может быть библиотекой с открытым исходным кодом для анализа tarball. Libarchive может читать каждый файл из архивного файла без извлечения, а также записывать данные для формирования нового архивного файла.