Встала тут передо мной задача изменить UID и GID пользователя и правильно изменить владельца всех файлов.
Дело в том, что я работаю за двумя компьютерами попеременно, и файлы mysql лежат у меня на флешке. Получилось так, что id пользователя mysql на обоих компах отличается и мускл не может получить доступ к своим файлам. Присваивать права 0666 скучно, и по этому поводу я решил научиться грамотно изменять uid пользователя :)
Казалось бы, всё просто, но есть два нюанса которые необходимо учесть:
- UID и GID не всегда одинаковы для пользователя и его группы
- Не все файлы принадлежат одновременно юзеру mysql и группе mysql: файлы для chown нужно искать отдельно
Изменение идентификатора пользователя и группы
Первый шаг — самый простой и о нём немало написано в интернетах. В первых двух строчках мы сохраняем полезные данные в переменных: имя, новый id и старый id для пользователя и для группы. Это нам пригодится в дальнейшем при поске файлов, а также упростит повторное использование скрипта. Всё указывается отдельно ввиду первого нюанса: UID и GID могут отличаться.
Дальше — тривиальное изменение идентификаторов стандартными командами Linux:
user=mysql new_uid=600 old_uid=$(id -u $user)
group=mysql new_gid=600 old_gid=$(id -g $user)
sudo usermod -u $new_uid $user
sudo groupmod -g $new_gid $group
Поиск осиротевших файлов
Если сейчас посмотреть на одну из папок с файлами mysql (например,
ls -lah /var/lib/mysql
) то мы увидим, что файлы принадлежат подозрительному пользователю 112 и подозрительной группе 127. Такие файлы мы и будем искать с тем, чтобы удочерить их :)Первое, что приходит в голову, это найти все файлы принадлежащие пользователю
$old_uid
или группе $old_group
, и собрать все найденные файлы (при помощи xargs) в качества аргументов команде chown $user:$group
. find
выполняется от root чтобы гарантировать что он сможет забраться во все даже самые сурово защищённые папки и найдёт всё что от него требуется. xargs
собирает строки из pipe и передаёт их команде, указанной в аргументе (chown). Замечу, что xargs
может выполнить команду несколько раз во избежание слишком длинной строки аргументов.Например, так:
sudo find / -user $old_uid -or -group $old_gid -print0 | xargs -0 sudo chown $user:$group
Сразу обращу внимание на флаги
find -print0
и xargs -0
: это такая борьба с возможными пробелами в именах файлов. Такие файлы могут быть восприняты chown
'ом как два разных. Первый флаг заставляет find
выводить каждый найденный файл с нулём в конце (символ конца строки в Си), а второй флаг сообщает xargs
что ему нужно отделять файлы друг от друга не по переводу строки, а по этому самому нулю, что гарантирует верную обработку даже самых хитрых имён файлов :)Однако такой способ не принесёт желаемого результата: некоторые файлы, владельцем которых был 'mysql:root' станут принадлежать 'mysql:mysql'. А мы ведь договорились сделать всё предельно правильно :) Следовательно, поиск по user и по group надо вести отдельно.
Можно выполнить подряд две команды find:
sudo find / -user $old_uid -print0 | xargs -0 sudo chown $user
sudo find / -group $old_gid -print0 | xargs -0 sudo chown :$group
и это уже будет намного ближе к истине, но тогда
find
'у придётся дважды шуршать по всему жёсткому диску.Есть способ заставить команду
find
выполнить для нас две операции параллельно, сократив количество чтений с диска ровно в два раза. Для этого используем группировку условий и команд find
круглыми скобками (не забывая их экранировать: иначе за них возьмётся шелл) и пославив между ними оператор «запятая»: тогда обе скобки будут выполняться для каждого файла.Мы составим два отдельных файла: в первом будет список файлов с
-user=$old_uid
, а во втором — с -group=$old_gid
, и обрабатывать эти файлы мы будет раздельно. Условие поиска теперь разделено на две выполняющися для каждого файла скобки, и в случае выполниния условия команда -fprint0
записывает в соответствующий временный файл путь к найденному осиротевшему файлу.chownlist=$(tempfile) chgrplist=$(tempfile)
sudo find / \
\( -user $old_uid -fprint0 "$chownlist" \) , \( -group $old_gid -fprint0 "$chgrplist" \)
После недолгого поиска все файлы будут найдены.
Последний момент поиска: на форумах очень часто задаётся вопрос как исключить папки из списка
find
так, чтобы он туда вообще не залезал. Это делается при помощи сочетания условия -path "folder"
и команды -prune
, которая запрещает find
залезать в папки, попавшие в условие. Мы исключим из поиска папки '/proc' и '/sys'.Для этого в условия добавим ещё одну группировку скобками, отделив их от уже существующих скобок оператором
-or
. Этот оператор выполнит первое условие, а второе — только если не сработало первое. Так, find
проверит не попалась ли ему исключённая из листинга директория (в которой он не будет искать), и если нет — будет составлять списки файлов.Делается это так:
chownlist=$(tempfile) chgrplist=$(tempfile)
sudo find / \
\( \( -path "/proc" -or -path "/sys" \) -prune \) -or \ # исключение папок
\( \( -user $old_uid -fprint0 "$chownlist" \) , \( -group $old_gid -fprint0 "$chgrplist" \) \)
Также в исключения можно внести путь к диску с бекапом (он ведь у вас есть, верно? ;), подмонтированные сетевые шары и прочее.
Финиш
Всё, списки составлены. Теперь для каждого списка файлов выполняем
chown
. Напомню, что мы старались полностью сохранить владельца файлов с учётом возможно отличающихся user и group.cat "$chownlist" | xargs -0 sudo chown $user
cat "$chgrplist" | xargs -0 sudo chown :$group
sudo rm "$chownlist" "$chgrplist" # Не забываем подчистить за собой
Проверка
Напоследок, проверим, не осталось ли где-нибудь файлов с неизвестными UID или GID. Для этого существует два полезных условия
find -nouser
и find -nogroup
. Полезно также уточнить поиск, исключив пресловутые '/proc' и '/sys' из поиска:sudo find / -nouser -or -nogroup -print
Если всё было сделано правильно (и в системе не водилось осиротевших файлов) — команда не должна ничего вывести.
Полный код скрипта
Надеюсь, статья окажется полезной. Рекомендую ближе ознакомиться с синтаксисом этой команды: она ведь намного мощнее чем вы думаете :)
Напоследок приведу полный код скрипта для смены UID&GID пользователя и его файлов:
#=== Настройки
user=mysql new_uid=600 old_uid=$(id -u $user) # имя, новый и старый UID
group=mysql new_gid=600 old_gid=$(id -g $user) # имя, новый и старый GID
#=== Смена UID & GID
sudo usermod -u $new_uid $user
sudo groupmod -g $new_gid $group
#=== Поиск файлов
chownlist=$(tempfile) chgrplist=$(tempfile) sudo find / \
\( \( -path "/proc" -or -path "/sys" \) -prune \) -or \
\( \( -user $old_uid -fprint0 "$chownlist" \) , \( -group $old_gid -fprint0 "$chgrplist" \) \)
#=== chown и чистка
cat "$chownlist" | xargs -0 sudo chown $user
cat "$chgrplist" | xargs -0 sudo chown :$group
sudo rm "$chownlist" "$chgrplist"