Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Работа с архивами tar и gz средствами PHP

Anna | 31.05.2014 | нет комментариев

Как это Зачастую случается, все началось с того, что мне понадобилось кое-что, разрешающее обрабатывать архивы «tar.gz» средствами php. Покопавшись в Интернете, я с изумлением нашел, что ничего сколько-нибудь приемлемого на эту тему не опубликовано.

Что у нас есть?

1. PEAR растяжение для PHP http://pear.php.net/package/Archive_Tar Восхитительно, но в моем случае – недопустимо, от того что доступа к настройкам сервера у меня нет. Вынужденно отметаем.
2. Хорошая статья Алексея Валеева «Работа с архивами tar.gz в php». То, что необходимо, но – увы. Мне требовалось решение лицензионно «прозрачное», не способное вызвать вопросов. По этому, применение библиотеки от Битрикса тоже не годилось.

Собственно, это – все.

Последующее просеивание поисковиков ничего умного не выдало. Немножко подумав, я забрался в код знаменитого net2ftp, тот, что, как я помню, восхитительно архивы tar обрабатывает. Выяснилось, что есть на свете библиотека pcltar.lib.php от Vincent Blavet, 2001 года. Лицензия GNU. Все, как нужно. Но! Для начала меня смутил размер самой библиотеки 127 килобайт. Ну, есть у меня бзик со ветхих времен — до сих пор считаю байты. Потом, мне хотелось иметь итог в виде класса, а не отдельными функциями. К тому же, взыграл энтузиазм. Захотелось разобраться исчерпывающе.

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

Выходит, как вестимо, tar архиватором в современном понимании не является. Разработанный для сохранения данных на ленточных носителях, сжимать он не может, а легко объединяет уйма файлов в один, добавляя свои заголовки, и дополняя получившийся код до ровного числа блоков по 512 байт. После этого итог теснее дозволено сжимать архиватором. Каким? Да, хоть rar-ом. Без разницы. Правда, обычно для этого применяются форматы gzip и bzip2. От того что они-то как раз два файла связать не могут (это диктуется принятой в unix-системах политикой «одна программа – одно действие»). Помощь gzip и bzip2 в PHP обеспечивается сторонними библиотеками, и нам не значима. Значим сам tar.

Коротко разберем конструкцию файла. Как и положено, сначала идет заголовок. Изучив документацию, я нашел, что есть «ветхий» и «новейший» форматы заголовка. Новейший — длиной 512 байт. Получили его, добавив в «ветхий» добавочные поля. Теоретически, они совместимы, но мы будем ориентироваться на современность. Испробуем его разобрать. Вот, если коротко, суть:

100 байт name — наименование (может содержать относительный путь);
8 байт mode file mode
8 байт uid — user ID
8 байт gid — group ID
12 байт size — размер файла, байт (кодирована в восьмеричной системе)
12 байт mtime – дата и время последней модификации в секундах эры UNIX (кодирована в восьмеричной системе)
8 байт chksum – контрольная сумма заголовка (не файла!)
1 байт typeflag – определяет файл у нас, либо каталог: файл – 0, каталог — 5
100 байт linkname – ссылка на файл
— дальше – поля «нового» формата — 6 байт magic – содержит слово «ustar», т.е. знак «нового» формата
2 байт version – версия нового формата (может отсутствовать)
32 байт uname – имя обладателя
32 байт gname – имя группы обладателя
8 байт devmajor – старший байт кода устройства
8 байт devminor – младший байт кода устройства
155 байт prefix – префикс (растяжение) имени

Не используемые байты обязаны быть пустыми, правда допускается код «20» (пробел).

Огромная часть этих данных в всеобщем случае не требуется. Лично меня волновали имя, размер и дата.

Дальше идет собственно информационная часть, дополняемая (внимание!) пустыми байтами до кратного 512 байт. И все снова для дальнейшего файла. Как видим, все легко.

В сущности, этих познаний довольно, Дабы испробовать запаковать файл.

1. Откроем архив командой fopen(имя_файла).

2. Заголовок. Это самая трудная часть задачи. Велосипед я изобретать не стал, воспользовавшись функцией изупомянутой библиотеки pcltar.lib.php, слегка её оптимизировав. Приводить тут каждый код не буду из-за объемности, но суть заключается в следующих действиях:
— Определяем имя файла, его размер, дату создания, выставленные на него права. Для каталогов размер указываем нулевым;
— Неиспользуемые параметры объявляем пустыми;
— Численные параметры (размер, дату) переводим в восьмеричную систему;
— Форматируем всякий параметр в соответствии с заявленными размерами соответствующих полей. Тут есть одна хитрость, с которой я разобрался не сразу – на самом деле, важная часть всякого поля должна быть на один байт поменьше, чем размер самого поля. Конечный же байт непременно должен быть пустым. Напротив архив не читается.
— Пакуем все параметры в две отдельные строки. В две, от того что между ними должна быть контрольная сумма заголовка.
— Считаем эту контрольную сумму, форматируем по тем же правилам, пакуем.
— А сейчас пишем в файл архива ступенчато три строки: первую часть параметров, контрольную сумму и вторую часть параметров.

Готово! Вот пример для времени создания:

$mtime = sprintf("%11s ", DecOct(filemtime($filename))); … pack("a100a8a8a8a12a12", …, …, …, …, …, $mtime);

3. С телом файла вовсе все легко, Vincent Blavet в своей библиотеке его тоже обрабатывает функцией pack. Но я провел несколько экспериментов с разными файлами и не увидел искажений при паковке / распаковке. По этому, ради выигрыша продуктивности, делать не стал – нет смысла. Легко читаем данные из файла, разумеется – заранее его открыв, и пишем в архив. От того что размеры файлов в моем случае могли оказаться довольно огромными, делаю я это блоками. Размер блока я принял за 50 Кб.

$infile = fopen($filename, rb); $j = ceil(filesize($filename) / 51200) 1; for($i=0; $i<$j; $i ){ $fr = fread($infile, 51200); if ($this->tarmode == "tar") @fputs($this->tarfile, $fr); else @gzputs($this->tarfile, $fr); } fclose($infile);

4. А сейчас добиваем до «ровного». Для этого нам необходимо знать сколько байт «не хватило». Если файл поменьше 512 байт, то это определяется вычитанием его размера из 512. Если же огромнее, определяем остаток от деления размера файла на 512, и вычитаем его из 512. Итог пакуем в бинарную строку.

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

Вот получившийся код:

$ffs = filesize($filename); if($ffs > 512) $tolast = 512 - fmod($ffs, 512); else $tolast = 512-$ffs; if($tolast != 512 && $tolast != 0){ $fdata = pack("a".$tolast, ""); Полученные данные записываем в файл. }

Итог – архив в формате «tar». Дозволено сейчас повторить операцию со дальнейшим файлом, либо закрыть архив.
Если у нас подключена библиотека Zlib, то в процессе создания архив дозволено сжать, получив в результате «tar.gz» либо «tgz», кому что нравится. Проверить присутствие библиотеки проще каждого путем проверки константы FORCE_GZIP. Для автоматизации процесса, я ввел такую проверку для всех операций с архивным файлом. Приблизительно, так:

if(defined('FORCE_GZIP')) $resopen = @fopen($this->tarname, 'a b'); else $resopen = @gzopen($this->tarname, 'a b'.$this->tarlevel);

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

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

Получить список файлов в архиве дозволено обнаружив и прочитав все заголовки. Для этого читаем первые 512 байт архива – это в любом случае будет заголовок и распаковываем его функцией unpack(). От того что unpack изготавливает распаковку в ассоциативный массив, заодно присваиваем параметрам внятные названия. Вот так:

unpack("a100name/a8perms/…и так далее…", “считанные данные”) 
Время создания и размер нужно перевести обратно в десятичное счисление.

Полученные параметры дозволено отдавать «на выход». Остается только сместить указатель в файле архива на считанный размер запакованного файла плюс остаток до 512-байтного блока. Сейчас он показывает на предисловие дальнейшего заголовка, и операцию дозволено повторить снова.

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

Исключительные две трудности тут, связаны с особенностями библиотеки Zlib:

Во-первых. Обнаружилось, что в функции gzopen данной библиотеки, не реализован модификант « » для открытия файла единовременно на запись и на чтение, подобно функции fopen. Пришлось отказаться от цельного открытия / закрытия файла архива, и повторять эти операции при всяком обращении, в соответствии с задачей.

Во-вторых, в документации указано (и я удостоверился в правдивости этого указания), что функция gzseek, аналогичная fseek «эмулируется, но работает весьма медлительно». Пришлось отказаться от прямого смещения указателя в файле архива на необходимую позицию, заменив его на «пустое» чтение, в урон продуктивности. Если бы дело ограничивалось только архивами tar, этого дозволено было бы избежать.

Вот, собственно, и все. В итоге, у меня получилась абсолютно универсальная библиотека, размером чуть огромнее 11 Кб не сжатого кода. Скачать библиотеку дозволено тут: Archivator_tar-tar_gz.zip.

Неизменно ваш, PunkerPoock

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB