Bug 34619

Summary: rpm неправильно обрабатывает замену симлинков
Product: Sisyphus Reporter: Aleksei Nikiforov <darktemplaralt>
Component: rpmAssignee: placeholder <placeholder>
Status: NEW --- QA Contact: qa-sisyphus
Severity: critical    
Priority: P3 CC: aen, antohami, arseny, bircoph, glebfm, ildar, imz, iv, lav, ldv, mike, placeholder, rider, slev, vseleznv, vt
Version: unstable   
Hardware: all   
OS: Linux   
See Also: https://bugzilla.altlinux.org/show_bug.cgi?id=36628
Bug Depends on:    
Bug Blocks: 33095, 34231, 35492, 41299, 42040, 36676, 42000, 42178, 43806    

Description Aleksei Nikiforov 2018-03-07 11:31:02 MSK
При обновлении пакета, если в пакете раньше был файл "А", являющимся симлинком на файл "B", а в новой версии пакета "А" становится обычным файлом (т.е. перестаёт быть симлинком на файл "B"), а файл "B" больше не содержится в новой версии, то в зависимости от неопределённых условий можно получить 1 из следующих результатов:

1) В системе будет новый файл "A", но также останется файл "B", который должен был быть удалён.
2) В системе не будет файла "B", но файл "A" останется симлинком на файл "B". Учитывая, что файла "B" нет, симлинк будет "битым".

Оба варианта неправильные, хоть первый обычно и менее вреден чем второй (и менее заметен).

Ожидаемый вариант:
Симлинк "А" будет заменён файлом "А", файл "B" будет удалён.

Также не проверялось поведение при замене в новой версии симлинка "A", указывающего на файл "B", и файла "B" на симлинк "A", указывающий на файл "C", и, соответственно, файл "C".

Думаю, аналогичные действия должны происходить при любой замене симлинков, в том числе на другие симлинки, регулярные файлы, директории или особые типы файлов.
Comment 1 Michael Shigorin 2018-03-11 17:40:29 MSK
Проблема известна давно.
Comment 2 Ivan A. Melnikov 2019-04-24 16:42:29 MSK
Кажется, что замена файла на симлинк на файл и обратно должна работать. Надо воспроизвести на маленьких простых спеках и посмотреть, что же там не так.

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

Замена симлинков на каталоги и обратно -- особый случай, к которому есть особый интерес. В RPM явно запрещено заменять каталог на что-либо ещё, и заменять симлинк на каталог, поскольку "We can't correctly handle [that]":

http://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/transaction.c;h=b55f11c0c2be5622887bdb1f06af3e8ff57812f7#l307

Нежелание иметь дело с такими обновлениями понятно: каталоги могут содержать файлы, в том числе файлы пользователя и файлы других пакетов; эти другие пакеты могут участвовать или не участвовать в текущей транзакции; наконец, ЕМНИП RPM "ходит по ссылкам" -- администратор может сам заменить какие-то каталоги на симлинки, и RPM (иногда) будет делать вид, что ничего не заметил. И на первый взгляд не очевидно, что можно специфицировать поведение RPM при замене симлинка на каталог или наоборот так, чтобы это поведение во всех случаях было понятным, очевидным и не приводило к нежелательным последствиям (вроде удаления файлов, которые на самом деле нужны, или "мусора" после удаления пакетов).

Нужно постараться понять, какие возможны ситуации, и что мы хотим от RPM в каждой из них.
Comment 3 alexey.tourbin 2019-04-24 19:00:22 MSK
Чтобы вынести каталог и заменить его на что-то еще, rpm должен доказать, что транзакция полностью выносит каталог со всем его содержимым и всеми подкаталогами. Это не невозможно.
Comment 4 Dmitry V. Levin 2019-04-24 19:55:54 MSK
Задача замены каталогов на ссылки выглядит более сложной, чем замена ссылок на каталоги.
Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок на каталоги, сперва решить задачу замены ссылок.
Comment 5 Dmitry V. Levin 2019-04-24 20:06:52 MSK
К сведению, сейчас в Сизифе 13 файлов, упакованных таким образом, что часть их пути является упакованной ссылкой на каталог:

/etc/init.d/puppetserver
/etc/init.d/xpra
/etc/init.d/xtreemfs-dir
/etc/init.d/xtreemfs-mrc
/etc/init.d/xtreemfs-osd
/usr/lib64/clip/etc/.macro
/usr/lib64/clip/etc/.templ
/usr/lib64/qt4/mkspecs/features/qcodeedit.prf
/usr/lib64/qt4/translations/qscintilla_cs.qm
/usr/lib64/qt4/translations/qscintilla_de.qm
/usr/lib64/qt4/translations/qscintilla_es.qm
/usr/lib64/qt4/translations/qscintilla_fr.qm
/usr/lib64/qt4/translations/qscintilla_pt_br.qm
Comment 6 Dmitry V. Levin 2019-04-26 03:58:44 MSK
(In reply to comment #4)
> Задача замены каталогов на ссылки выглядит более сложной, чем замена ссылок на
> каталоги.
> Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок
> на каталоги, сперва решить задачу замены ссылок.

Раньше, когда возникала задача замены ссылок каталогами, мы всегда создавали %pre-скрипты, в котором просто удаляли эти ссылки.

Оказывается, в Федоре для замены ссылок каталогами теперь предлагают создавать %pretrans-скрипты, в котором просто удаляются эти ссылки:
https://docs.fedoraproject.org/en-US/packaging-guidelines/Directory_Replacement/#_scriptlet_to_replace_a_symlink_to_a_directory_with_a_directory

Поскольку все, не сговариваясь, до сих пор предлагают в качестве решения задачи замены ссылок каталогами простое удаление этих ссылкок, предлагаю не искать других вариантов и реализовать это поведение непосредственно в rpm по-умолчанию.
Comment 7 AEN 2019-04-26 10:12:07 MSK
(В ответ на комментарий №6)
> (In reply to comment #4)
> > Задача замены каталогов на ссылки выглядит более сложной, чем замена ссылок на
> > каталоги.
> > Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок
> > на каталоги, сперва решить задачу замены ссылок.
> 
> Раньше, когда возникала задача замены ссылок каталогами, мы всегда создавали
> %pre-скрипты, в котором просто удаляли эти ссылки.
> 
> Оказывается, в Федоре для замены ссылок каталогами теперь предлагают создавать
> %pretrans-скрипты, в котором просто удаляются эти ссылки:
> https://docs.fedoraproject.org/en-US/packaging-guidelines/Directory_Replacement/#_scriptlet_to_replace_a_symlink_to_a_directory_with_a_directory
> 
> Поскольку все, не сговариваясь, до сих пор предлагают в качестве решения задачи
> замены ссылок каталогами простое удаление этих ссылкок, предлагаю не искать
> других вариантов и реализовать это поведение непосредственно в rpm
> по-умолчанию.
Давайте.
Кто?
Иван, попробуете? Глеб сейчас в ppc64le по уши.
Comment 8 Vladimir D. Seleznev 2019-04-26 21:29:02 MSK
(In reply to comment #6)
> (In reply to comment #4)
> > Задача замены каталогов на ссылки выглядит более сложной, чем замена ссылок на
> > каталоги.
> > Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок
> > на каталоги, сперва решить задачу замены ссылок.
> 
> Раньше, когда возникала задача замены ссылок каталогами, мы всегда создавали
> %pre-скрипты, в котором просто удаляли эти ссылки.
> 
> Оказывается, в Федоре для замены ссылок каталогами теперь предлагают создавать
> %pretrans-скрипты, в котором просто удаляются эти ссылки:
> https://docs.fedoraproject.org/en-US/packaging-guidelines/Directory_Replacement/#_scriptlet_to_replace_a_symlink_to_a_directory_with_a_directory
> 
> Поскольку все, не сговариваясь, до сих пор предлагают в качестве решения задачи
> замены ссылок каталогами простое удаление этих ссылкок, предлагаю не искать
> других вариантов и реализовать это поведение непосредственно в rpm
> по-умолчанию.

А пакеты, использующие механизм alternatives, не сломаются?
Comment 9 Dmitry V. Levin 2019-04-26 22:08:34 MSK
(In reply to comment #8)
> (In reply to comment #6)
> > (In reply to comment #4)
> > > Задача замены каталогов на ссылки выглядит более сложной, чем замена ссылок на
> > > каталоги.
> > > Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок
> > > на каталоги, сперва решить задачу замены ссылок.
> > 
> > Раньше, когда возникала задача замены ссылок каталогами, мы всегда создавали
> > %pre-скрипты, в котором просто удаляли эти ссылки.
> > 
> > Оказывается, в Федоре для замены ссылок каталогами теперь предлагают создавать
> > %pretrans-скрипты, в котором просто удаляются эти ссылки:
> > https://docs.fedoraproject.org/en-US/packaging-guidelines/Directory_Replacement/#_scriptlet_to_replace_a_symlink_to_a_directory_with_a_directory
> > 
> > Поскольку все, не сговариваясь, до сих пор предлагают в качестве решения задачи
> > замены ссылок каталогами простое удаление этих ссылкок, предлагаю не искать
> > других вариантов и реализовать это поведение непосредственно в rpm
> > по-умолчанию.
> 
> А пакеты, использующие механизм alternatives, не сломаются?

Всё, что могло сломаться, уже сломано.  Если не сделать новых ошибок, то дополнительно ничего не сломается, и замена ссылок каталогами будет исправлена.
Comment 10 Ivan A. Melnikov 2019-04-29 16:20:05 MSK
(In reply to comment #0)
> При обновлении пакета, если в пакете раньше был файл "А", являющимся симлинком
> на файл "B", а в новой версии пакета "А" становится обычным файлом (т.е.
> перестаёт быть симлинком на файл "B"), а файл "B" больше не содержится в новой
> версии, то в зависимости от неопределённых условий можно получить 1 из
> следующих результатов:
> 
> 1) В системе будет новый файл "A", но также останется файл "B", который должен
> был быть удалён.
> 2) В системе не будет файла "B", но файл "A" останется симлинком на файл "B".
> Учитывая, что файла "B" нет, симлинк будет "битым".

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

http://git.altlinux.org/people/iv/public/?p=rpm-update-tests.git;a=tree

Оба варинта тестировал несколько раз, всё отработало штатно, проблема
не воспроизвелась (Сизиф, rpm-4.13.0.1-alt6.x86_64).
Comment 11 AEN 2019-04-29 19:48:25 MSK
(In reply to comment #10)
> (In reply to comment #0)
> > При обновлении пакета, если в пакете раньше был файл "А", являющимся симлинком
> > на файл "B", а в новой версии пакета "А" становится обычным файлом (т.е.
> > перестаёт быть симлинком на файл "B"), а файл "B" больше не содержится в новой
> > версии, то в зависимости от неопределённых условий можно получить 1 из
> > следующих результатов:
> > 
> > 1) В системе будет новый файл "A", но также останется файл "B", который должен
> > был быть удалён.
> > 2) В системе не будет файла "B", но файл "A" останется симлинком на файл "B".
> > Учитывая, что файла "B" нет, симлинк будет "битым".
> 
> Я решил начать с понятного и воспроизвести проблему с файлами. Для этого я
> накидал
> несколько маленьких спек-файлов, в которых реализуется этот и обратный
> сценарии:
> 
> http://git.altlinux.org/people/iv/public/?p=rpm-update-tests.git;a=tree
> 
> Оба варинта тестировал несколько раз, всё отработало штатно, проблема
> не воспроизвелась (Сизиф, rpm-4.13.0.1-alt6.x86_64).

Проблема возникает по крайней мере при обновлении систем p8 , содержащих lua5.1 и gdb
Из письма cas@:
"Попытка обновления 64-битного Альт Рабочая станция, Альт Образование и
Альт Сервер (после успешной установки пакетов rpm apt):

Совершаем изменения... 
Подготовка...                          
#############################################################################
[100%]
        файл /usr/lib64/lua/5.1 из устанавливаемого пакета
liblua5.1-5.1.5-alt15.x86_64 конфликтует с файлом из пакета
lua5.1-alt-compat-1.0-alt1.x86_64
        файл /usr/share/lua/5.1 из устанавливаемого пакета
liblua5.1-5.1.5-alt15.x86_64 конфликтует с файлом из пакета
lua5.1-alt-compat-1.0-alt1.x86_64
E: Ошибка во время исполнения транзакции
(при этом говорит, что будет занято дополнительно 907 МБ).

Для Альт Образование ещё конфликт:

    файл /usr/share/gdb/python/gdb из устанавливаемого пакета
gdb-8.2.50.20180917-alt2.x86_64 конфликтует с файлом из пакета
gdb-7.9-alt3.x86_64

Альт Образование 8 вообще рекордсмен по количеству удаляемых пакетов - 45:

Следующие пакеты будут УДАЛЕНЫ:
  certmonger freeipa-client gimagereader-qt4 gst-plugins-bad
  i586-ICAClient-preinstall.32bit i586-libGLdispatch.32bit
  i586-libbrotlicommon0.32bit i586-libbrotlidec0.32bit i586-libcurl.32bit
  i586-nspluginwrapper.32bit i586-nvidia_glx_384.111.32bit
  i586-nvidia_glx_384.98.32bit kde5 kde5-mini kde5-network-manager-4-nm
  kde5-small kdenlive libcairo-devel libgtk+2-devel libopencv2.4
libpango-devel
  librrd4 lua5.1-alt-compat omsclient.32bit plasma5-desktop plasma5-kwin
  plasma5-nm-connect-openconnect plasma5-nm-maxi plasma5-workspace
  python-module-freeipa python-module-ipa_hbac python-module-ipaclient
  python-module-ipaclient-ntp python3-module-yieldfrom.requests syslogd
  task-auth-freeipa virtualbox-guest-additions vlc-plugin-a52
  vlc-plugin-bonjour vlc-plugin-dca vlc-plugin-goom vlc-plugin-mad
xfce4-mixer
  xorg-drv-openchrome xorg-drv-vboxvideo
"
Comment 12 Ivan A. Melnikov 2019-04-30 11:26:04 MSK
(In reply to comment #11)
> (In reply to comment #10)
> > (In reply to comment #0)
> > > При обновлении пакета, если в пакете раньше был файл "А", являющимся симлинком
> > > на файл "B", а в новой версии пакета "А" становится обычным файлом (т.е.
> > > перестаёт быть симлинком на файл "B"), а файл "B" больше не содержится в новой
> > > версии, то в зависимости от неопределённых условий можно получить 1 из
> > > следующих результатов:
> > > 
> > > 1) В системе будет новый файл "A", но также останется файл "B", который должен
> > > был быть удалён.
> > > 2) В системе не будет файла "B", но файл "A" останется симлинком на файл "B".
> > > Учитывая, что файла "B" нет, симлинк будет "битым".
[...]
> > Оба варинта тестировал несколько раз, всё отработало штатно, проблема
> > не воспроизвелась (Сизиф, rpm-4.13.0.1-alt6.x86_64).
> 
> Проблема возникает по крайней мере при обновлении систем p8 , содержащих lua5.1
> и gdb

Проблема с lua5.1 и gdb -- это проблема замены каталогов на симлинки и/или наоборот. Как я писал выше, такие замены намеренно заблокированы в коде RPM, и  здесь мы должны поменять это поведение чтобы исправить проблему (ну то есть заменить её на другую, менее неприятную).

Однако darktemplar@ в описании этого бага пишет про проблемы с симлинками на обычные файлы. Такая проблема мне не знакома. Если она действительно есть, то начинать разбираться надо именно с неё. Однако на простых примерах мне её не удалось воспроизвести.

darktemplar@, а Вы на чём проверяли это?
Comment 13 Aleksei Nikiforov 2019-04-30 16:48:03 MSK
(В ответ на комментарий №12)
> (In reply to comment #11)
> > (In reply to comment #10)
> > > (In reply to comment #0)
> > > > При обновлении пакета, если в пакете раньше был файл "А", являющимся симлинком
> > > > на файл "B", а в новой версии пакета "А" становится обычным файлом (т.е.
> > > > перестаёт быть симлинком на файл "B"), а файл "B" больше не содержится в новой
> > > > версии, то в зависимости от неопределённых условий можно получить 1 из
> > > > следующих результатов:
> > > > 
> > > > 1) В системе будет новый файл "A", но также останется файл "B", который должен
> > > > был быть удалён.
> > > > 2) В системе не будет файла "B", но файл "A" останется симлинком на файл "B".
> > > > Учитывая, что файла "B" нет, симлинк будет "битым".
> [...]
> > > Оба варинта тестировал несколько раз, всё отработало штатно, проблема
> > > не воспроизвелась (Сизиф, rpm-4.13.0.1-alt6.x86_64).
> > 
> > Проблема возникает по крайней мере при обновлении систем p8 , содержащих lua5.1
> > и gdb
> 
> Проблема с lua5.1 и gdb -- это проблема замены каталогов на симлинки и/или
> наоборот. Как я писал выше, такие замены намеренно заблокированы в коде RPM, и 
> здесь мы должны поменять это поведение чтобы исправить проблему (ну то есть
> заменить её на другую, менее неприятную).
> 
> Однако darktemplar@ в описании этого бага пишет про проблемы с симлинками на
> обычные файлы. Такая проблема мне не знакома. Если она действительно есть, то
> начинать разбираться надо именно с неё. Однако на простых примерах мне её не
> удалось воспроизвести.
> 
> darktemplar@, а Вы на чём проверяли это?

Если я правильно помню, нарвался я на такую проблему когда работал над очередным пакетом. Пакет с такими проблемами в репозиторий я не отправлял, и похоже рабочего репозитория не сохранилось за ненадобностью. Возможно, описанная мной проблема не возникает на простых случаях или была уже исправлена.
Comment 14 Ivan A. Melnikov 2019-06-13 13:01:10 MSK
> Поэтому, а также потому, что все текущие баг-репорты именно про замену ссылок
> на каталоги, сперва решить задачу замены ссылок.

Я вижу три способа решения проблемы:

(1) удалить симлинк непосредственно перед
    созданием каталога, в процессе установки пакета;

(2) сделать отдельный проход по всем пакетам,
    собрать все симлинки, которые нужно заменять на
    каталоги и удалить их перед началом транзакции
    -- такой захардкоженый pretrans trigger.

(3) в каком-то виде вернуть предыдущее поведение,
    позволив пакетам исправлять праблему самим
    из %pre-скрипта

Способ (1) кажется наиболее интересным, так как на первый
взгляд меньше всего похож на грязный хак и больше всего
на правильный шаг на пути к идеалу. Я попробовал его реализовать.

Нужно учесть, что rpm "ходит по ссылкам", и это поведение
нужно сохранить. Это значит, что при установке пакета
необходимо различать два случая: когда симлинк будет
удалён в той же транзакции (и тогда его нужно удалить
заранее) и когда симлинк трогать не нужно (тогда
достаточно проверить, что это симлинк на каталог).
Чтобы разделить эти случаи, проще всего добавить
новый action в rpmFileAction. Тогда можно выставить
этот action для каталога из свежего пакета при
анализе транзакции. В первом приближении получается
следующее:

http://git.altlinux.org/people/iv/packages/rpm.git?p=rpm.git;a=commitdiff;h=c33b836afae663c4bbef14d6f59945d7872cc24a


Однако этого недостаточно. Рассмотрим простейший
пример, когда вместе с исходным каталогом переехали
файлы.

http://git.altlinux.org/people/iv/public/?p=rpm-update-tests.git;a=commitdiff;h=b247c271ad7dd6641284631876db0065e8055bf1

Было:

drwxr-xr-x 3 root root 4096 мая 13 17:27 /usr/share/rpm-test-symlink2dir
drwxr-xr-x 2 root root 4096 мая 13 17:27 /usr/share/rpm-test-symlink2dir/a
-rw-r--r-- 1 root root   46 мая 13 17:26 /usr/share/rpm-test-symlink2dir/a/x.txt
lrwxrwxrwx 1 root root    1 мая 13 17:26 /usr/share/rpm-test-symlink2dir/b -> a

Стало:

drwxr-xr-x 4 root root 4096 мая 13 17:39 /usr/share/rpm-test-symlink2dir
drwxr-xr-x 2 root root 4096 мая 13 17:39 /usr/share/rpm-test-symlink2dir/b
-rw-r--r-- 1 root root   46 мая 13 17:26 /usr/share/rpm-test-symlink2dir/b/x.txt

Здесь при анализе транзакции RPM увидит, что в новой версии пакета
есть /usr/share/rpm-test-symlink2dir/b/x.txt; с точки зрения RPM
он заменяет существующий /usr/share/rpm-test-symlink2dir/b/x.txt,
а значит при удалении старой версии пакета этот файл не нужно трогать.
В итоге, по результатам обновления остаётся "мусор": старый
`x.txt` вместе со своим каталогом `a`.

drwxr-xr-x 4 root root 4096 мая 13 18:30 /usr/share/rpm-test-symlink2dir/
drwxr-xr-x 2 root root 4096 мая 13 18:29 /usr/share/rpm-test-symlink2dir/a
-rw-r--r-- 1 root root   46 мая 13 18:18 /usr/share/rpm-test-symlink2dir/a/x.txt
drwxr-xr-x 2 root root 4096 мая 13 18:30 /usr/share/rpm-test-symlink2dir/b
-rw-r--r-- 1 root root   46 мая 13 18:18 /usr/share/rpm-test-symlink2dir/b/x.txt

Причина такого поведения понятна: fingerprint cache
доверяет состоянию файловой системы на момент
начала транзакции:

http://git.altlinux.org/people/iv/packages/rpm.git?p=rpm.git;a=blob;f=lib/fprint.c;h=65eaeec66e2a0804648f3ce9bade8a34f99e8249#l194

Переделать fingerprint cache чтобы он правильно
обрабатывал замену ссылки на каталог на каталог
я пока не успел; похоже, для этого требуется
достаточно серьёзно переписать doLookupId()
из lib/fprint.c.
Comment 15 Ivan A. Melnikov 2019-06-13 13:04:39 MSK
> (1) удалить симлинк непосредственно перед
>     созданием каталога, в процессе установки пакета;
>
> (2) сделать отдельный проход по всем пакетам,
>     собрать все симлинки, которые нужно заменять на
>     каталоги и удалить их перед началом транзакции
>     -- такой захардкоженый pretrans trigger.
>
> (3) в каком-то виде вернуть предыдущее поведение,
>     позволив пакетам исправлять праблему самим
>     из %pre-скрипта

Учитывая вышесказанное, (2) кажется более простым способом получить решение прямо сейчас. Это означает, что нужно специально пройтись по всем пакетам, построить fingerPrintCache, вычислить с его помощью симлинки которые нужно удалить, удалить их и заново построить fingerPringCache. Имхо ужас, но работать будет.
Comment 16 AEN 2019-06-17 05:13:12 MSK
(В ответ на комментарий №15)
> > (1) удалить симлинк непосредственно перед
> >     созданием каталога, в процессе установки пакета;
> >
> > (2) сделать отдельный проход по всем пакетам,
> >     собрать все симлинки, которые нужно заменять на
> >     каталоги и удалить их перед началом транзакции
> >     -- такой захардкоженый pretrans trigger.
> >
> > (3) в каком-то виде вернуть предыдущее поведение,
> >     позволив пакетам исправлять праблему самим
> >     из %pre-скрипта
> 
> Учитывая вышесказанное, (2) кажется более простым способом получить решение
> прямо сейчас. Это означает, что нужно специально пройтись по всем пакетам,
> построить fingerPrintCache, вычислить с его помощью симлинки которые нужно
> удалить, удалить их и заново построить fingerPringCache. Имхо ужас, но работать
> будет.
Коллеги, согласны?
Comment 17 Ivan Zakharyaschev 2019-06-17 15:29:56 MSK
(In reply to comment #14)

> Я вижу три способа решения проблемы:
> 
> (1) удалить симлинк непосредственно перед
>     созданием каталога, в процессе установки пакета;
> 
> (2) сделать отдельный проход по всем пакетам,
>     собрать все симлинки, которые нужно заменять на
>     каталоги и удалить их перед началом транзакции
>     -- такой захардкоженый pretrans trigger.
> 
> (3) в каком-то виде вернуть предыдущее поведение,
>     позволив пакетам исправлять праблему самим
>     из %pre-скрипта

Мы нацеливались на (2). При этом о таком и похожих примерах я беспокоился (попробую записать, что именно мне казалось важным):

> Однако этого недостаточно. Рассмотрим простейший
> пример, когда вместе с исходным каталогом переехали
> файлы.
> 
> http://git.altlinux.org/people/iv/public/?p=rpm-update-tests.git;a=commitdiff;h=b247c271ad7dd6641284631876db0065e8055bf1
> 
> Было:
> 
> drwxr-xr-x 3 root root 4096 мая 13 17:27 /usr/share/rpm-test-symlink2dir
> drwxr-xr-x 2 root root 4096 мая 13 17:27 /usr/share/rpm-test-symlink2dir/a
> -rw-r--r-- 1 root root   46 мая 13 17:26
> /usr/share/rpm-test-symlink2dir/a/x.txt
> lrwxrwxrwx 1 root root    1 мая 13 17:26 /usr/share/rpm-test-symlink2dir/b -> a
> 
> Стало:
> 
> drwxr-xr-x 4 root root 4096 мая 13 17:39 /usr/share/rpm-test-symlink2dir
> drwxr-xr-x 2 root root 4096 мая 13 17:39 /usr/share/rpm-test-symlink2dir/b
> -rw-r--r-- 1 root root   46 мая 13 17:26
> /usr/share/rpm-test-symlink2dir/b/x.txt
> 
> Здесь при анализе транзакции RPM увидит, что в новой версии пакета
> есть /usr/share/rpm-test-symlink2dir/b/x.txt; с точки зрения RPM
> он заменяет существующий /usr/share/rpm-test-symlink2dir/b/x.txt,
> а значит при удалении старой версии пакета этот файл не нужно трогать.
> В итоге, по результатам обновления остаётся "мусор": старый
> `x.txt` вместе со своим каталогом `a`.

Было содержание пакета (повторяю, чтобы подчеркнуть, что путь файла пакета понимается как b/x.txt, т.е. использующий симлинк):

%dir a
b -> a
b/x.txt

Стало:

%dir b
b/x.txt

На самом деле, раньше файл лежал по реальному пути a/x.txt и выполнял роль b/x.txt благодаря тому, что был симлинк b -> a (такая как бы зависимость от наличия такого симлинка).

Старая версия пакета удаляется вместе с симлинком, должен удаляться и файл {b -> a}/x.txt, а создаваться b/x.txt. Сейчас насколкьо я понимаю, это плохо вписывается в процедуру erasing в конце, если не проводить различия между путями {b -> a}/x.txt и b/x.txt (придумал в голове такое обозначение чтобы их различать).

Теперь ещё можно рассмотреть пример, когда было установлено два пакета (X, Y).

X:
b -> a

Y:
b/x.txt

Файл из пакета Y реально лежит по пути a/x.txt. Если X обновляется на X:
%dir b

то Y потеряет свой файл. Можно это представить так, что X содержал симлинк b -> a, Y -- файл по пути {b -> a}/x.txt. Когда X исчезает с {b -> a}, нужно сообщать об ошибке (конфликтах в предпринятом действии): для пакета Y был нужен симлинк.

То же самое (конфликт/ошибке) по моему представлению должно возникнуть, когда один симлинк заменяется на другой в таком случае:

Y имеет файл {b -> a}/x.txt, а X при обновлении меняет {b -> a} на {b -> c}. В таких условиях Y не мог бы больше существовать, поэтому надо сообщить об ошибке при попытке так сделать.
Comment 18 AEN 2019-06-17 15:37:31 MSK
А много ли проблемных пакетов? В банках описаны два: gdb и lua. Не проще ли уменьшить общность задачи?
Comment 19 Dmitry V. Levin 2019-06-17 15:40:16 MSK
(In reply to comment #17)
> Было содержание пакета (повторяю, чтобы подчеркнуть, что путь файла пакета
> понимается как b/x.txt, т.е. использующий симлинк):
> 
> %dir a
> b -> a
> b/x.txt

Есть гипотеза, что в Сизифе такого нет.
Comment 20 Ivan Zakharyaschev 2019-06-17 16:01:16 MSK
(In reply to comment #19)
> (In reply to comment #17)
> > Было содержание пакета (повторяю, чтобы подчеркнуть, что путь файла пакета
> > понимается как b/x.txt, т.е. использующий симлинк):
> > 
> > %dir a
> > b -> a
> > b/x.txt
> 
> Есть гипотеза, что в Сизифе такого нет.

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

Возможно, так достаточно просто, чтобы реализовать поведение, достаточное для двух известных проблемных пакетов.
Comment 21 Anton Farygin 2019-06-18 08:08:38 MSK
в apt'е есть обвязка для запуска скрипт до, во время и после транзации.
Можем ли мы его использовать для решения проблемы с симлинками?

      pkgRPMPM::RunScriptsWithPkgs("RPM::Pre-Install-Pkgs")
      _lua->RunScripts("Scripts::PM::Pre");
      _lua->RunScripts("Scripts::PM::Post");
      Ret = RunScripts("RPM::Post-Invoke");
      RunScripts("RPM::Post-Invoke");
Comment 22 Ivan Zakharyaschev 2019-06-18 12:05:46 MSK
(In reply to comment #21)
> в apt'е есть обвязка для запуска скрипт до, во время и после транзации.
> Можем ли мы его использовать для решения проблемы с симлинками?
> 
>       pkgRPMPM::RunScriptsWithPkgs("RPM::Pre-Install-Pkgs")
>       _lua->RunScripts("Scripts::PM::Pre");
>       _lua->RunScripts("Scripts::PM::Post");
>       Ret = RunScripts("RPM::Post-Invoke");
>       RunScripts("RPM::Post-Invoke");

Как некий workaround можно было бы. Но я не уверен, что rpm в таком случае всё равно не будет жаловаться на замену как на конфликт исходя из свое базы.
Comment 23 Anton Farygin 2019-06-18 16:12:43 MSK
Т.к. таких пакетов еденицы, то прежде чем перепахивать rpm можно попробовать реализовать такой workaround на apt'е и посмотреть к чему это приведёт.
Comment 24 Ivan Zakharyaschev 2019-06-18 17:07:01 MSK
(In reply to comment #22)
> (In reply to comment #21)
> > в apt'е есть обвязка для запуска скрипт до, во время и после транзации.
> > Можем ли мы его использовать для решения проблемы с симлинками?
> > 
> >       pkgRPMPM::RunScriptsWithPkgs("RPM::Pre-Install-Pkgs")
> >       _lua->RunScripts("Scripts::PM::Pre");
> >       _lua->RunScripts("Scripts::PM::Post");
> >       Ret = RunScripts("RPM::Post-Invoke");
> >       RunScripts("RPM::Post-Invoke");
> 
> Как некий workaround можно было бы. Но я не уверен, что rpm в таком случае всё
> равно не будет жаловаться на замену как на конфликт исходя из свое базы.

Да, похоже, rpm это переживёт, судя по коду:

static int handleRemovalConflict(rpmfiles fi, int fx, rpmfiles ofi, int ofx)
{
    int rConflicts = 0; /* Removed files don't conflict, normally */
    rpmFileTypes ft = rpmfiWhatis(rpmfilesFMode(fi, fx));
    rpmFileTypes oft = rpmfiWhatis(rpmfilesFMode(ofi, ofx));
    struct stat sb;
    char *fn = NULL;

    if (oft == XDIR) {
	/* We can't handle directory changing to anything else */
	if (ft != XDIR)
	    rConflicts = 1;
    } else if (oft == LINK) {
	/* We can't correctly handle directory symlink changing to directory */
	if (ft == XDIR) {

Здесь проверяем, что это не просто симлинк по базе данных, а именно sym- или hardlink на директорию.

	    fn = rpmfilesFN(fi, fx);
	    if (stat(fn, &sb) == 0 && S_ISDIR(sb.st_mode))
		rConflicts = 1;
	}
    }

    /*
     * ...but if the conflicting item is either not on disk, or has
     * already been changed to the new type, we should be ok afterall.
     */
    if (rConflicts) {
	if (fn == NULL)
	    fn = rpmfilesFN(fi, fx);

Здесь отменяем конфликт, если, в частности, hardlink на директорию (т.е. симлинка нет уже) или если оно вообще отсутствует. (В общем случае -- если отсутствует или реальный тип совпадает с новым.)

	if (lstat(fn, &sb) || rpmfiWhatis(sb.st_mode) == ft)
	    rConflicts = 0;
    }

    free(fn);
    return rConflicts;
}

Так что если apt перед транзакцией удалит симлинк у проблемных пакетов, то должно проскочить. Мои опасения были неверными.

Нужно ещё подумать, как такой pretrans-скрипт для этих пакетов донести до apt.
Comment 25 Dmitry V. Levin 2019-06-18 17:29:32 MSK
Неужели скрипт для apt окажется проще?  Не верится.
Comment 26 Gleb F-Malinovskiy 2019-06-18 17:31:33 MSK
(In reply to comment #21)
> в apt'е есть обвязка для запуска скрипт до, во время и после транзации.
> Можем ли мы его использовать для решения проблемы с симлинками?
> 
>       pkgRPMPM::RunScriptsWithPkgs("RPM::Pre-Install-Pkgs")
>       _lua->RunScripts("Scripts::PM::Pre");
>       _lua->RunScripts("Scripts::PM::Post");
>       Ret = RunScripts("RPM::Post-Invoke");
>       RunScripts("RPM::Post-Invoke");

Этот подход ничем не лучше %pretrans .  Только хуже потому что
* при установке пакетов rpm-ом ничего не произойдёт;
* скрипты для apt-а нужно установить до обновления пакетов в которых лежат проблемные пути.
Comment 27 Ivan Zakharyaschev 2019-06-18 17:33:39 MSK
(In reply to comment #25)
> Неужели скрипт для apt окажется проще?  Не верится.

Тут скорее речь об общности решения. Для apt скрипт -- просто для двух известных пакетов делает то, что известно, что для них можно сделать. (Аналог pretrans-скрипта, где ответственность лежит на мейнтейнере пакета, который написал такой скрипт.)

А в rpm хардкодить надо более общее с дополнительной защитой от разрушения установленной системы.
Comment 28 Anton Farygin 2022-09-16 12:14:34 MSK
А почему бы всё-таки не затащить %pretrans в наш rpm и не стать в этом плане ближе к остальным ?
Comment 29 Stanislav Levin 2022-09-21 17:15:15 MSK
Есть ли на сегодня варианты объезда (симлинк на директорию => директория)?
Comment 30 Антон Мидюков 2022-10-05 05:18:35 MSK
(Ответ для Anton Farygin на комментарий #28)
> А почему бы всё-таки не затащить %pretrans в наш rpm и не стать в этом плане
> ближе к остальным ?

Других вариантов не просматривается.

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

Поддержка исправлений в коде потребует всё возрастающих затрат от мантейнера. Примером такого пакета, который к тому же собирается роботом - firmware-linux. Я добавил превентивную проверку на возникновения такой ситуации в cronbuild скрипте, но инструмента для решения таких проблем у меня просто нет. Придётся править апстримный код. В итоге может получиться, что в пакете не окажется нужного симлинка на firmware (в следствие моей ошибки при возникновении конфликта), а сам пакет всё чаще придётся собирать руками. Поэтому прошу дать мантейнерам такой инструмент.
Comment 31 Anton Farygin 2023-10-01 16:28:31 MSK
есть шанс починки этой ошибки в p11 ?
Comment 32 Arseny Maslennikov 2023-10-01 20:37:02 MSK
(In reply to Anton Farygin from comment #31)
> есть шанс починки этой ошибки в p11 ?

На вики есть страница, на ней параграфы с рецептами миграции каталога на симлинк и симлинка на каталог.
https://www.altlinux.org/RPM/pretrans
Раз в Sisyphus это поддерживается, значит, будет и в p11.