Как извлечь все данные из архива bzip2 с помощью C?

У меня есть составной файл, состоящий из некоторого количества bzip2 архивов. Я также знаю размеры отдельных фрагментов bzip2 в этом файле.

Я хотел бы распаковать поток bzip2 из отдельного фрагмента данных bzip2 и записать вывод в стандартный вывод.

Сначала я использую fseek для перемещения курсора файла к нужному байту архива, а затем считываю «размер»-фрагмент файла в вызов BZ2_bzRead:

int headerSize = 1234;
int firstChunkSize = 123456;
FILE *fp = fopen("pathToConcatenatedFile", "r+b");
char *bzBuf = malloc(sizeof(char) * firstChunkSize);
int bzError, bzNBuf;
BZFILE *bzFp = BZ2_bzReadOpen(&bzError, *fp, 0, 0, NULL, 0);

# move cursor past header of known size, to the first bzip2 "chunk"
fseek(*fp, headerSize, SEEK_SET); 

while (bzError != BZ_STREAM_END) {
    # read the first chunk of known size, decompress it
    bzNBuf = BZ2_bzRead(&bzError, bzFp, bzBuf, firstChunkSize);
    fprintf(stdout, bzBuf);
}

BZ2_bzReadClose(&bzError, bzFp);
free(bzBuf);
fclose(fp);

Проблема в том, что когда я сравниваю вывод инструкции fprintf с выводом запуска bzip2 в командной строке, я получаю два разных ответа.

В частности, я получаю меньше результатов от этого кода, чем от запуска bzip2 в командной строке.

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

С помощью другого метода я проверил, что командная строка bzip2 дает правильный ответ, и, следовательно, какая-то проблема с моим кодом C приводит к тому, что вывод в конце фрагмента отсутствует. Я просто не знаю, что это за проблема.

Если вы знакомы с bzip2 или libbzip2, можете ли вы дать совет, что я делаю неправильно в приведенном выше примере кода? Спасибо за ваш совет.


person Alex Reynolds    schedule 12.10.2010    source источник
comment
Есть ли в файле байты ASCII NUL (нулевые байты)? Есть ли в данных символы процента? Вы используете fprintf() очень опасно - вам, вероятно, следует использовать fputs() или даже fwrite(), но в противном случае используйте fprintf(stdout, "%s", bzBuf);.   -  person Jonathan Leffler    schedule 12.10.2010
comment
Я получаю тот же результат с fputs(bzBuf, stdout) или fprintf(stdout, "%s", bzBuf). Насколько мне известно, фрагменты bzip2 являются буквенно-цифровыми, символами новой строки и табуляции. Во входных данных, которые использовались для создания фрагментов bzip2, нет символов процента или нулевых символов, которые можно успешно распаковать с помощью инструмента командной строки bzip2.   -  person Alex Reynolds    schedule 12.10.2010
comment
ОК - вам повезло с символами процентов; отсутствие нулевых байтов не так уж и необычно. Суммировали ли вы количество байтов, возвращенных в bzNBuf, чтобы увидеть, соответствует ли то, что возвращают операции чтения, тому, что вы получаете на выходе, или тому, что получает bzip2 при работе с файлом? Ваш комментарий о «прочтении первого фрагмента известного размера» вводит в заблуждение - в любом случае, после того, как он прочитал один фрагмент. Файл, который вы распаковываете, больше, чем firstChunkSize?   -  person Jonathan Leffler    schedule 12.10.2010
comment
Кроме того, вам, вероятно, следует проверить bzError после BZ2_bzRead() и перед fprintf(). Однако это, скорее всего, приведет к дополнительным данным, чем к слишком малым. (Вы уверены, что ваш процесс дает меньший результат, чем bzip2?)   -  person Jonathan Leffler    schedule 12.10.2010
comment
Независимо от того, где я fseek в своем объединенном файле, я получаю меньший результат. Относится ли параметр size в API к размеру читаемого фрагмента bzip2 или к размеру несжатого вывода? Из чтения API кажется, что это первый вариант, но я вполне могу ошибаться.   -  person Alex Reynolds    schedule 12.10.2010
comment
Наконец, от меня сегодня вечером: поскольку я не вижу информации о том, что BZ2_bzRead() null завершает свой вывод, я думаю, вам действительно следует беспокоиться о возвращаемых длинах и о том, не ошибаетесь ли вы в данных. Но наиболее вероятные способы неправильного управления им приводят к большему результату, а не к меньшему, поэтому я не уверен, что проблема в этом.   -  person Jonathan Leffler    schedule 12.10.2010
comment
Ну, почти наконец - в руководстве LIBBZ2 сказано, что BZ2_bzRead( ) 'Читает до len (несжатых) байт из сжатого файла'. Это имеет смысл; вы сообщаете ему, сколько места находится в вашем буфере вывода, и он не выходит за пределы этого пространства с несжатым выводом.   -  person Jonathan Leffler    schedule 12.10.2010
comment
Вы правы, и я думаю, что еще одна проблема заключается в том, что я использую strtok для bzBuf, токенизируя новые строки, когда bzBuf, вероятно, не имеет всех данных, которые мне нужны, за один раз. Понятно, почему я теряю то, что в конце — размер bzBuf — это размер архива bzip2, а не несжатых данных, и поэтому при следующей заливке в bzBuf данные не разбиваются на новые строки.   -  person Alex Reynolds    schedule 12.10.2010
comment
разве ваш вызов BZ2_bzReadOpen() не должен использовать fp вместо *fp?   -  person mwag    schedule 27.09.2019


Ответы (1)


Это мой исходный код:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <bzlib.h>

int
bunzip_one(FILE *f) {
  int bzError;
  BZFILE *bzf;
  char buf[4096];

  bzf = BZ2_bzReadOpen(&bzError, f, 0, 0, NULL, 0);
  if (bzError != BZ_OK) {
    fprintf(stderr, "E: BZ2_bzReadOpen: %d\n", bzError);
    return -1;
  }

  while (bzError == BZ_OK) {
    int nread = BZ2_bzRead(&bzError, bzf, buf, sizeof buf);
    if (bzError == BZ_OK || bzError == BZ_STREAM_END) {
      size_t nwritten = fwrite(buf, 1, nread, stdout);
      if (nwritten != (size_t) nread) {
        fprintf(stderr, "E: short write\n");
        return -1;
      }
    }
  }

  if (bzError != BZ_STREAM_END) {
    fprintf(stderr, "E: bzip error after read: %d\n", bzError);
    return -1;
  }

  BZ2_bzReadClose(&bzError, bzf);
  return 0;
}

int
bunzip_many(const char *fname) {
  FILE *f;

  f = fopen(fname, "rb");
  if (f == NULL) {
    perror(fname);
    return -1;
  }

  fseek(f, 0, SEEK_SET);
  if (bunzip_one(f) == -1)
    return -1;

  fseek(f, 42, SEEK_SET); /* hello.bz2 is 42 bytes long in my case */
  if (bunzip_one(f) == -1)
    return -1;

  fclose(f);
  return 0;
}

int
main(int argc, char **argv) {
  if (argc < 2) {
    fprintf(stderr, "usage: bunz <fname>\n");
    return EXIT_FAILURE;
  }
  return bunzip_many(argv[1]) != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
  • Я очень заботился о правильной проверке ошибок. Например, я убедился, что bzError равно BZ_OK или BZ_STREAM_END, прежде чем пытаться получить доступ к буферу. В документации четко сказано, что для других значений bzError возвращаемое число не определено.
  • Вас не должно пугать, что около 50 процентов кода связано с обработкой ошибок. Вот как это должно быть. Ожидайте ошибок везде.
  • В коде все еще есть ошибки. В случае ошибок он не освобождает ресурсы (f, bzf) должным образом.

И это команды, которые я использовал для тестирования:

$ echo hello > hello
$ echo world > world
$ bzip2 hello
$ bzip2 world
$ cat hello.bz2 world.bz2 > helloworld.bz2
$ gcc -W -Wall -Os -o bunz bunz.c -lbz2
$ ls -l *.bz2
-rw-r--r-- 1 roland None 42 Oct 12 09:26 hello.bz2
-rw-r--r-- 1 roland None 86 Oct 12 09:36 helloworld.bz2
-rw-r--r-- 1 roland None 44 Oct 12 09:26 world.bz2
$ ./bunz.exe helloworld.bz2 
hello
world
person Roland Illig    schedule 12.10.2010