То, что вы прочитали, сильно преувеличено.
Совершенно безопасно перезаписать сценарий оболочки на месте, mv
вставив в него другой файл. При этом старый дескриптор файла остается действительным, ссылаясь на исходный неизмененный файл. содержание. То, что вы не можете безопасно сделать, это отредактировать существующий файл на месте.
Итак, ниже все в порядке (и это то, что делают все ваши инструменты обновления поставщиков ОС, такие как RPM):
#!/usr/bin/env bash
tempfile=$(mktemp "$BASH_SOURCE".XXXXXX)
if curl https://example.com/whatever >"$tempfile" &&
curl https://example.com/whatever.sig >"$tempfile.sig" &&
gpgv "$tempfile.sig" "$tempfile"; then
chown --reference="$BASH_SOURCE" -- "$tempfile"
chmod --reference="$BASH_SOURCE" -- "$tempfile"
sync # force your filesystem to fully flush file contents to disk
mv -- "$tempfile" "$BASH_SOURCE" && rm -f -- "$tempfile.sig"
else
rm -f -- "$tempfile" "$tempfile.sig"
exit 1
fi
... тогда как это рискованно:
curl https://example.com/whatever >/usr/local/bin/whatever
Так что сделайте первое, а не второе: при загрузке новой версии вашего скрипта запишите это в другой файл и переименуйте его поверх оригинала только после успешной загрузки. Это то, что вы хотите сделать, чтобы обеспечить атомарность.
(Есть также некоторые демонстрации методов проверки подписи кода выше, потому что, ну, они вам нужны при создании средства обновления. Вы не пытались бы распространять код с помощью автоматической загрузки без проверки подписи, верно «Потому что именно так один простой прорыв к вашему веб-серверу приводит к тому, что каждый ваш клиент получает 0wned. Выше ожидается, что открытая сторона ваших ключей для подписи кода будет в ~/.gnupg/trustedkeys.gpg
, но вы можете поставить trustedkeys.gpg
в любом и укажите на него переменную окружения GNUPGHOME
).
Даже если вы не напишите свой код обновления безопасно, риск по-прежнему тривиален, чтобы уменьшить его. Если вы переместите основную часть вашего скрипта в функцию, так что она должна быть полностью прочитана перед любая его часть может быть выполнена, но в тот момент, когда начинается выполнение, нет той части файла, которая еще не была прочитана.
#!/usr/bin/env bash
main() {
echo "Logic all goes here"
}; { main; exit; }
Поскольку { main; exit; }
является частью составной команды, синтаксический анализатор читает exit
до того, как он начнет выполнять main
, поэтому гарантируется, что после завершения работы main
дальнейшее содержимое исходного файла не будет прочитано, , даже если какой-то будущий выпуск bash не обрабатывал ввод строки построчно .