Как стать автором
Обновить

Изменение UID&GID пользователя и его файлов

Время на прочтение 5 мин
Количество просмотров 32K
reuid.png - image uploaded to Picamatic
Встала тут передо мной задача изменить UID и GID пользователя и правильно изменить владельца всех файлов.
Дело в том, что я работаю за двумя компьютерами попеременно, и файлы mysql лежат у меня на флешке. Получилось так, что id пользователя mysql на обоих компах отличается и мускл не может получить доступ к своим файлам. Присваивать права 0666 скучно, и по этому поводу я решил научиться грамотно изменять uid пользователя :)

Казалось бы, всё просто, но есть два нюанса которые необходимо учесть:
  1. UID и GID не всегда одинаковы для пользователя и его группы
  2. Не все файлы принадлежат одновременно юзеру mysql и группе mysql: файлы для chown нужно искать отдельно
Статья написана для тех, кто ещё не делал ничего подобного а также кто хочет научиться продвинутому использованию команды find и узнать что такое xargs.



Изменение идентификатора пользователя и группы

Первый шаг — самый простой и о нём немало написано в интернетах. В первых двух строчках мы сохраняем полезные данные в переменных: имя, новый 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"
Теги:
Хабы:
+6
Комментарии 16
Комментарии Комментарии 16

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн