Назад | Перейти на главную страницу

Tar не сохраняет права доступа к каталогу

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

# TARGET (extract):
$ nc -l 2222 | pigz -d | sudo tar xpf - --same-owner -C /

# SOURCE: 
$ tar -cf - -T selected-images-to-copy.txt | pigz | pv | nc 1.1.1.1 2222

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

Если я не извлекаю tar, а вместо этого просматриваю имеющееся у меня содержимое:

$ tar tvzf test.tar.gz
-rw-r--r-- root/www-data 319434 2017-09-23 05:47 mnt/a/b/c/0012Z.jpg
-rw-r--r-- root/www-data 323647 2017-09-23 05:47 mnt/a/b/c/0005Z.jpg
-rw-r--r-- root/www-data 315962 2017-09-23 05:47 mnt/a/b/c/0013Z.jpg
-rw-r--r-- root/www-data 313594 2017-09-23 05:47 mnt/a/b/c/0007Z.jpg

Однако при извлечении все папки, созданные экстрактом между mnt и изображением, принадлежат root: root и имеют разрешения 0750, что означает, что они недоступны никому, кроме root.

$ sudo ls -al mnt/a/b
total 12
drwxr-x--- 3 root root 4096 Oct  6 15:01 .
drwxr-x--- 3 root root 4096 Oct  6 15:01 ..
drwxr-x--- 3 root root 4096 Oct  6 15:01 c

Из-за количества файлов рекурсивные операции, такие как chown и chmod, будут выполняться бесконечно. У нас есть собственный сценарий python, который изменяет разрешения, но опять же это добавляет дни к процессу; поэтому я хотел бы получить разрешения прямо из коробки, если это возможно.

Примечание: исследуя это, я нашел этот вопрос об ошибке сервера, который вызывает аналогичную проблему, но был сделан вывод, что это ошибка, исправленная в tar v1.24.

$ tar --version
tar (GNU tar) 1.27.1

Если selected-images-to-copy.txt представляет собой только список файлов (последний элемент пути всегда является файлом, а не каталогом) вот решение для создания архива с соответствующими правами на каталог:

РЕДАКТИРОВАТЬ: Я добавил лучшее решение в конце, сохранив промежуточные решения, используя комментарии dave_thompson_085 и думая о том, что можно улучшить с помощью доступной информации.
Как он писал (и поскольку я не объяснял полностью), важной частью решения является использование --no-recursion. Это позволяет сохранять всю метаинформацию для каждого вручную добавленного каталога в пути, вплоть до самих файлов, без включения всех других нежелательных каталогов и файлов, которые в противном случае были бы добавлены рекурсивно.

awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt > selected-images-to-copy-with-explicit-arborescences.txt
tar cf - --no-recursion -T selected-images-to-copy-with-explicit-arborescences.txt | pigz | pv | nc 1.1.1.1 2222

Если вы действительно хотите делать это на лету, используя bash <() построить:

tar cf - --no-recursion -T <(awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt) | pigz | pv | nc 1.1.1.1 2222

Команда awk просто реконструирует и добавляет путь, по одному уровню каталога за раз, вплоть до самого файла.

Таким образом, любой каталог на пути к сохраняемому файлу также помещается в архив, но с --no-recursion больше ничего не произойдет. Таким образом, каждое владение каталогом перед файлом будет сохранено и восстановлено правильно.

По-прежнему существует проблема производительности, которую вы должны где-то торговать: будет много повторяющихся ветвлений, поэтому второй tar будет часто повторять chown в том же базовом каталоге. Вы можете отсортировать -u результат awk, чтобы удалить все эти дубликаты, но тогда сортировка может занять очень много времени, прежде чем выдаст результаты и начнется передача. С коротким Perl-скриптом, который будет хранить уникальные элементы в памяти (компромисс - это использование памяти, но я сомневаюсь, что это проблема), нет необходимости в сортировке для вывода уникальных записей без задержки. Итак, решение становится:

tar cf - --no-recursion -T <(awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt | perl -w -e 'use strict; my %unique; while (<>) { if (not $unique{$_}++) { print } }'  ) | pigz | pv | nc 1.1.1.1 2222

РЕДАКТИРОВАТЬ: Если содержание selected-images-to-copy.txt представляет собой более или менее отсортированный список файлов (несортированный вывод find [...] -type f вид команды достаточно хорош), вот решение, которое не требует использования памяти (что действительно могло стать проблемой с сотнями миллионов записей). Достаточно просто запомнить последний самый длинный путь и сравнить его с следующий путь:
- либо следующий не является префиксом предыдущего, что означает, что это новый файл древовидности (или новый файл в том же самом древе) и должен быть заархивирован, и в этом случае создается новый "последний самый длинный путь". Если исходный список не был хотя бы представлен в виде дерева (как минимум find вывод команды или, конечно, отсортированный список), появятся некоторые начальные повторения.
- либо это префикс (подстрока, соответствующая 1-му символу), то есть это каталог, который уже был просмотрен, поскольку он является частью пути к предыдущему, и его можно безопасно игнорировать.

Я добавляю конечный / в сравнении, чтобы легко найти, что mnt/a/b/foo/ не является префиксом mnt/a/b/foobar . С участием mnt/a/b/foobar/file4.png и mnt/a/b/foo/file5.png в качестве входных данных право собственности на каталог mnt/a/b/foo не было бы восстановлено без этой уловки. Таким образом, команда perl заменяется на:

awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }'

Этот образец:

file1.png
mnt/a/b/file2.png
mnt/a/b/file3.png
mnt/a/b/c/foobar/file4.png
mnt/a/b/c/foo/file5.png
mnt/a/b/file6.png
mnt/a/b/d/file7.png

Через этот фильтр:

awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' | awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }'

Предоставляет эти каталоги и файлы, готовые для tar --no-recursion:

file1.png
mnt
mnt/a
mnt/a/b
mnt/a/b/file2.png
mnt/a/b/file3.png
mnt/a/b/c
mnt/a/b/c/foobar
mnt/a/b/c/foobar/file4.png
mnt/a/b/c/foo
mnt/a/b/c/foo/file5.png
mnt/a/b/file6.png
mnt/a/b/d
mnt/a/b/d/file7.png

Таким образом, решение со всей парой команд становится (root уже использует -p и --same-owner, а лучше отказаться от фантазии bash <() когда | может работать и легко позволяет разорвать длинную очередь с \ для удобочитаемости):

# TARGET (extract):
$ nc -l -p 2222 | pigz -d | sudo tar xf - -C /

# SOURCE: 
$ awk -F/ '{ d=$1; for (i=2; i <= NF; i++) { print d; d=d "/" $i }; print d }' selected-images-to-copy.txt | \
      awk '{ if (index(old,$0 "/") != 1) { old=$0; print } }' | \
      tar cf - --no-recursion -T - | pigz | pv | nc -w 60 1.1.1.1 2222
  • При создании используйте -p для сохранения разрешений (tar -cpvf file.tar bla bla)
  • При извлечении pass --same-owner опция tar. (tar -xvf --same-owner file.tar)