Как я нашёл баг в GNU Tar

Original author: Chris Siebenmann
  • Translation
Автор статьи — Крис Зибенманн, системный администратор Unix в университете Торонто

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

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

(Из-за этого мне стало интересно, появляются ли нулевые байты естественным образом у людей в почтовых ящиках. Оказалось, что поиск нулевых байтов в текстовых файлах не такой простой и да, они там есть).

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

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 является чьим-то почтовым ящиком.

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

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

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

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

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

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

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

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

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

Comments 12

    +4

    Два замечания


    Я очень ценю труд пользователя m1rko Статьи реально полезные и выходят достаточно часто. Но можно попросить чуточку более аккуратно относиться к переводу (знаки препинания, ну, конечно, и орфография)


    Во-вторых, статья вообще о чём? Что парни нашли баг в tar'е? Да вообще молодцы — видимо про lvm snapshot не слышали. Я уж не говорю о том, что бекапить каталоги и файлы, пока в них пишут — верх слабоумия. Очень двойственное впечатление. С одной стороны — да, действительно tar с ключом sparse должен работать предсказуемо, а с другой — бекап через одно место

      +2
      Некоторые товарищи запуститли апгрейд(типа того) системы совместно с резайзом картинок.
      Была недавно статья на тему как гарантированно уронить систему и мучительно её поднимать, сидя на удалёнке за много-много милионов километров.
      От тех самых товарищей.
        0
        Спасибо за ссылку. Прям мощь. Чуть было не потеряли космический аппарат. На этот раз повезло и помогли профессионализм и хладнокровие инженеров.
        0
        Но можно попросить чуточку более аккуратно относиться к переводу (знаки препинания, ну, конечно, и орфография)

        Пользуясь случаем, оставлю ссылку на сервис проверки грамматики) (правда сам редко им пользуюсь)
        languagetool.org/ru
        +1
        Написал на bug-tar@gnu.org

        Но вообще, я даже не уверен, что архиватор должен уметь корректно работать с изменяющимся во время архивации файлом.
        +9
        Как я нашел баг в TAR…
        … и ничего не сделал, чтобы его исправить, потому что «университет на зимних каникулах».

        Какой странный дядя.
          +4
          статью написал.
            0
            О да! Великий труд! :)
            0

            Пожалуй недоумение вызывает уже сам факт того, что для того, чтоб зарепортить баг нужен кто-то, кто не на "зимних каникулах" O_o

            0
            del, не та ветка
              0
              К моменту публикации перевода на хабре фикс уже был готов:
              git.savannah.gnu.org/cgit/tar.git/commit/?id=c15c42c

              Only users with full accounts can post comments. Log in, please.