Шаг 19 - Чтение ZIP файла в Linux с помощью библиотеки libzip

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

В данной ситуации очень помогают библиотеки созданные кем-то ранее и начинаешь понимать силу Open Source. Так вот с помощью исследования просторов интернета и нескольких программ различной направленности я случайно нашел для себя библиотеку libzip (http://nih.at/libzip) для работы с ZIP архивами.

К сожалению в поставке Slackware 12.2 я такой библиотеки не нашел. Поэтому скачиваем ее:

wget http://nih.at/libzip/libzip-0.9.tar.bz2

Распаковываем архив:

tar xvjf libzip-0.9.tar.bz2

Переходим внутрь директории с библиотекой и компилируем ее:

cd libzip-0.9
./configure --prefix=/usr
make && make install

Теперь библиотека установлена.

Давайте для теста создадим специальный ZIP архив, а для этого создадим структуру каталогов с файлами:

mkdir /testzip
cd /testzip
mkdir dir1
mkdir dir2
echo "file 1 in dir 1" > dir1/file1.txt
echo "file 2 in dir 1" > dir1/file2.txt
echo "file 1 in dir 2" > dir2/file1.txt
echo "file 2 in dir 2" > dir2/file2.txt
echo "file 1 in root" > file1.txt
echo "file 2 in root" > file2.txt

Теперь архивируем это все:

root@darkstar:/testzip# zip test.zip -R *
  adding: dir1/ (stored 0%)
  adding: dir1/file1.txt (stored 0%)
  adding: dir1/file2.txt (stored 0%)
  adding: dir2/ (stored 0%)
  adding: dir2/file1.txt (stored 0%)
  adding: dir2/file2.txt (stored 0%)
  adding: file1.txt (stored 0%)
  adding: file2.txt (stored 0%)

root@darkstar:/testzip# unzip -l test.zip
Archive:  test.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
       16  03-22-09 09:40   dir1/file1.txt
       16  03-22-09 09:40   dir1/file2.txt
       16  03-22-09 09:40   dir2/file1.txt
       16  03-22-09 09:40   dir2/file2.txt
       15  03-22-09 09:40   file1.txt
       15  03-22-09 09:40   file2.txt
 --------                   -------
       94                   6 files

Теперь давайте напишем простейшую программу для работы с ZIP файлом:

// файл zip1.c
#include <stdio.h>
#include <stdlib.h>
#include <zip.h>

int main(int argc, char **argv) {
	struct zip *zip_file; // дескриптор zip файла
	int err; // переменая для возврата кодов ошибок
	int files_total; // количество файлов в архиве

	if (argc < 2) {
	    fprintf(stderr,"usage: %s <zipfile>\n",argv[0]);
		return -1;
	};

	// открываем файл zip с именем переданным в качестве параметра
	zip_file = zip_open(argv[1], 0, &err);
	if (!zip_file) {
		fprintf(stderr,"Error: can't open file %s\n",argv[1]);
		return -1;
	};

	files_total = zip_get_num_files(zip_file); // количество файлов в архиве
	printf("Files in ZIP: %d\n",files_total);
	
	zip_close(zip_file);

	return 0;
};

Компилируем и пробуем запускать:

root@darkstar:/testzip# gcc -lzip zip1.c -o zip1

root@darkstar:/testzip# ./zip1
usage: ./zip1 <zipfile>

root@darkstar:/testzip# ./zip1 1.zip
Error: can't open file 1.zip

root@darkstar:/testzip# ./zip1 test.zip
Files in ZIP: 6

Видим то, что файл открывается и мы можем узнать сколько в архиве файлов.

Для получения списка имен файлов в архиве давайте создадим файл zip2.c:

// файл zip2.c
#include <stdio.h>
#include <stdlib.h>
#include <zip.h>

int main(int argc, char **argv) {
	struct zip *zip_file; // дескриптор zip файла
	struct zip_stat file_info; // информация о файле
	struct tm file_time;
	int err; // переменая для возврата кодов ошибок
	int files_total; // количество файлов в архиве
	int i;

	if (argc < 2) {
		fprintf(stderr,"usage: %s <zipfile>\n",argv[0]);
		return -1;
	};

	// открываем файл zip с именем переданным в качестве параметра
	zip_file = zip_open(argv[1], 0, &err);
	if (!zip_file) {
		fprintf(stderr,"Error: can't open file %s\n",argv[1]);
		return -1;
	};

	files_total = zip_get_num_files(zip_file); // количество файлов в архиве
	printf("Files in ZIP: %d\n",files_total);

	for (i = 0; i < files_total; i++) {
		// получаем информацию о файле с номером i в структуру file_info
		zip_stat_index(zip_file, i, 0, &file_info);

		printf("index=%d ",file_info.index); // номер файла

		printf("name=\"%s\" ",file_info.name); // имя файла

		printf("size=%u ",file_info.size); // размер файла

		localtime_r(&file_info.mtime, &file_time); // дата модификации файла
		printf("date=\"%02d-%02d-%04d %02d:%02d\"",
			file_time.tm_mday,file_time.tm_mon+1,
			file_time.tm_year+1900, file_time.tm_hour,
			file_time.tm_min);

		printf("\n");
	};	

	zip_close(zip_file);

	return 0;
};

Компилируем и тестируем:

root@darkstar:/testzip# gcc -lzip zip2.c -o zip2
root@darkstar:/testzip# ./zip1 test.zip
Files in ZIP: 6
index=0 name="dir1/file1.txt" size=16 date="22-03-2009 09:40"
index=1 name="dir1/file2.txt" size=16 date="22-03-2009 09:40"
index=2 name="dir2/file1.txt" size=16 date="22-03-2009 09:40"
index=3 name="dir2/file2.txt" size=16 date="22-03-2009 09:40"
index=4 name="file1.txt" size=15 date="22-03-2009 09:40"
index=5 name="file2.txt" size=15 date="22-03-2009 09:40"

Заметьте, что у нас легко и непринужденно получился аналог команды unzip -l test.zip со своим форматом вывода данных.

Модифицируем немного исходник для того, чтобы извлекать из него данные:

// файл zip3.c
#include <stdio.h>
#include <stdlib.h>
#include <zip.h>

int main(int argc, char **argv) {
	struct zip *zip_file; // дескриптор zip файла
	struct zip_file *file_in_zip; // дексриптор файла внутри архива
	int err; // переменая для возврата кодов ошибок
	int files_total; // количество файлов в архиве
	int file_number;
	int r;
	char buffer[10000];

	if (argc < 3) {
	    fprintf(stderr,"usage: %s <zipfile> <fileindex>\n",argv[0]);
		return -1;
	};

	// открываем файл zip с именем переданным в качестве параметра
	zip_file = zip_open(argv[1], 0, &err);
	if (!zip_file) {
		fprintf(stderr,"Error: can't open file %s\n",argv[1]);
		return -1;
	};

	file_number = atoi(argv[2]); // номер файла берем из 3 аргумента
	files_total = zip_get_num_files(zip_file); // количество файлов в архиве
	if (file_number > files_total) {
		printf("Error: we have only %d files in ZIP\n",files_total);
		return -1;
	};

	// открываем файл внутри архива по номеру file_number
	file_in_zip = zip_fopen_index(zip_file, file_number, 0);
	if (file_in_zip) {
		// читаем в цикле содержимое файла и выводим
		while ( (r = zip_fread(file_in_zip, buffer, sizeof(buffer))) > 0) {
			printf("%s",buffer);
		};
		// закрываем файл внутри архива
		zip_fclose(file_in_zip);
	} else {
		fprintf(stderr,"Error: can't open file %d in zip\n",file_number);
	};

	zip_close(zip_file);

	return 0;
};

Компилируем и пробуем запустить:

root@darkstar:/testzip# gcc -lzip zip3.c -o zip3

root@darkstar:/testzip# ./zip3
usage: ./zip3 <zipfile> <fileindex>

root@darkstar:/testzip# ./zip3 test.zip 10
Error: we have only 6 files in ZIP

root@darkstar:/testzip# ./zip3 test.zip 5
file 2 in root

root@darkstar:/testzip# ./zip3 test.zip 2
file 1 in dir 2

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

Библиотека libzip естественно имеет функции и для добавления файлов в архив, но это можно будет рассмотреть потом или же те, кому это понадобится, самостоятельно найдут решение.

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


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Кузин Андрей.