СофтХабрахабр

[Перевод] Как я нашёл баг в GNU Tar

Автор статьи — Крис Зибенманн, системный администратор Unix в университете Торонто

Недавно я упомянул, что мы нашли ошибку в GNU Tar, и история о том, как это произошло, — один из таких случаев. Время от времени в моей работе происходит нечто странное, что заставляет задуматься, даже если сразу непонятно, какие следуют выводы.

В течение долгого времени у нас периодически возникала довольно редкая проблема, когда tar сходил с ума при резервном копировании файловой системы с каталогом /var/mail, производя огромное количество выходных данных. Для бэкапа файл-серверов мы используем Amanda и GNU Tar. Когда мне в очередной раз попался такой гигантский файл tar, я подверг его проверке — и выяснил, что он частично состоит из нулевых байтов, которые очень не нравятся команде тестирования tar -t, после чего всё возвращается в норму. Обычно этот процесс уходил в бесконечность и приходилось убивать дамп; в других случаях он всё-таки завершался, выдав терабайт(ы) данных, которые вроде бы отлично сжимались.

Оказалось, что поиск нулевых байтов в текстовых файлах не такой простой, и да, они там есть).
Недавно мы перенесли файловую систему с /var/mail на новые файловые серверы Linux под Ubuntu 18. (Из-за этого мне стало интересно, появляются ли нулевые байты естественным образом у людей в почтовых ящиках. Мы надеялись, что это решит наши проблемы, но почти сразу произошёл такой же инцидент. 04 и поэтому перешли на более позднюю и более стандартную версию GNU Tar, чем стоит на машинах OmniOS. Проверка показала, что tar выдаёт бесконечный поток read(), возвращающих 0 байт: На этот раз GNU Tar работал на машине Ubuntu, где я хорошо знаком со всеми доступными инструментами отладки, так что я проверил запущенный процесс tar.

read(6, "", 512) = 0
read(6, "", 512) = 0
[...]
read(6, "", 512) = 0
write(1, "\0\0\0\0\0"..., 10240) = 10240
read(6, "", 512) = 0
[...]

lsof сказал, что файловый дескриптор 6 является чьим-то почтовым ящиком.

Разобрав несколько уровней косвенной адресации, я нашёл очевидное место, где вроде бы такая проверка опущена, а именно в функции sparse_dump_region из файла sparse.cs. С помощью apt-get source tar я загрузил исходный код и начал искать в нём системные вызовы read(), которые не проверяют завершение файла. И тут я кое-что вспомнил.

Работая над этим багом, я сделал трассировку процесса Alpine и заметил, среди прочего, что для изменения размера почтовых ящиков он использует ftruncate(); иногда он расширяет их, временно создавая разреженный раздел файла, пока не заполнит его, и, возможно, иногда сжимает его. Несколько месяцев назад мы столкнулись с проблемой NFS в Alpine. Казалось, это совпадало с нынешней ситуацией: разреженные области связаны, а сокращение размера файла с помощью ftruncate() создаёт ситуацию, когда tar неожиданно для себя сталкивается с завершением файла.

(Это даже объясняет, почему tar иногда восстанавливается; если позже в ящик вдруг пришла новая почта, тот возвращается к ожидаемому размеру, и tar больше не сталкивается с неожиданным завершением файла).

Оказалось, что sparse_dump_region не сбрасывает разреженные области файла, а сбрасывает не разреженные (ну конечно), и используется для всех файлов (разреженные или нет), если вы запускаете tar с аргументом --sparse. Я немного повозился в GDB на отладочных символах Ubuntu и исходном коде пакета tar, который я получил, и смог воспроизвести ошибку, хотя она несколько отличалась от моей первоначальной теории. Если файл снова увеличивается, tar восстанавливает работу. Таким образом, фактическая ошибка заключается в том, что если вы запускаете GNU Tar с аргументом --sparse и файл сжимается в процессе его чтения, tar не может правильно обработать конец файла, полученный ранее, чем ожидалось.

В таком случае всё в порядке). (За исключением случаев, когда файл разрежен только в конце и сжимается только в этом месте.

Там есть способы трассировки системных вызовов программы и аналоги lsof, и я мог бы найти и посмотреть исходный код своей версии GNU Tar и запустить его с отладчиком OmniOS (хотя там у нас вроде не установлена GDB), и так далее. Мне подумалось, что всё то же самое я мог проверить много лет назад на наших файл-серверах OmniOS. Вместо этого мы пожали плечами и двинулись дальше. Но я этого не сделал. Потребовалось переместить файловую систему под Ubuntu, чтобы я пошевелил пальцем и разобрался в проблеме.

(Дело не только в инструментах и окружении; мы ещё автоматически предположили, что на OmniOS стоит какая-то старая неподдерживаемая версия GNU Tar, которую нет смысла исследовать, ведь проблему конечно же решили в более новой версии).

S.: Наверное, в качестве быстрого исправления мы просто запретим Amanda использовать параметр tar --sparse при резервном копировании. P. Почтовые ящики не должны быть разреженными, а если такое случится, мы всё равно сжимаем бэкапы файловой системы, так что все эти нулевые байты хорошо сожмутся.

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть