At one of our customer we will have an monstre’ UID-consolidation projekt in the near future. For this purpose I’ve written a script to enlighten our task. Most of you thinks at it a an over-complicated (usermod && find …. chown …) job, but my script has undergone some performance optimisation too: it starts parallel (find….chown) processes to get the job done as fast as it can be. Here is the output for changing the UID of a testuser:
# ./chguid testuser 234 Checking parameters OK Checking for duplicate UID OK Changing the UID in the password database OK The UID was successfully changed: Before: testuser:x:123:20:test:/home/testuser:/usr/bin/ksh After: testuser:x:234:20:test:/home/testuser:/usr/bin/ksh Starting parallel processes to change ownership Threads with chown: (the list may be incomplete on fast systems) root 4993 4981 7 13:54:04 pts/0 0:00 find / -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + root 4995 1 7 13:54:04 pts/0 0:00 find /orae6/hes1 -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + root 4994 1 5 13:54:04 pts/0 0:00 find /orae6/heb1 -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + FINISHED #
and here is the script:
#!/usr/bin/ksh # # Author: Viktor Balogh # Version: v0.4 # function usage { print "\n Usage: chguid USERNAME NEW_UID\n" print " Where USERNAME is the name of an existing user and UID is between 0 and 2147483647." print " This UID needs to be unique, as this will be the new UID of the user.\n" print " The UID of the system users cannot be changed for security reasons.\n" } function check_root { # check if we have enough permission if [ $(id -u) -ne "0" ] then printf " %s\n\n" "You should be root in order to use this tool!" exit 6 fi } function check_param { # check the parameters of the script printf " %-60s" "Checking parameters" if [ $# -ne 2 ] then printf "%s\n" "ERROR" printf " %s\n\n" "Please feed exactly two parameters!" usage exit 2 elif $(pwget -n ${UNAME} 1>/dev/null 2>/dev/null) then # UID_MAX is 2147483647 in limits.h, so 2147483646 is the highest UID # we do not touch UID 0 and system users if [[ "${UID}" -lt "2147483647" && "${UID}" -gt "0" && "${UNAME}" != "root" && "${UNAME}" != "daemon" && "${UNAME}" != "bin" && "${UNAME}" != "sys" && "${UNAME}" != "adm" && "${UNAME}" != "uucp" && "${UNAME}" != "lp" && "${UNAME}" != "nuucp" && "${UNAME}" != "hpdb" && "${UNAME}" != "nobody" ]] then # check if UID already exists printf "%s\n" "OK" printf " %-60s" "Checking for duplicate UID" UID_SEARCH=$(pwget | awk -F: '$3 == "'${UID}'"' 2>/dev/null) if [ ! -z "${UID_SEARCH}" ] then printf "%s\n" "ERROR" printf " %s\n\n" "The given UID already exists! Please specify another one!" usage exit 5 else printf "%s\n" "OK" fi else printf "%s\n" "ERROR" printf " %s\n\n" "Invalid UID/UNAME!" usage exit 4 fi else printf "%s\n" "ERROR" printf " %s\n\n" "User not found!" exit 3 fi } function check_trusted { ## OBSOLETE : usermod also handles trusted # check if system is trusted printf " %-60s" "Checking if system is trusted" /usr/lbin/getprdef -r 1>/dev/null 2>/dev/null if [ $? -eq 4 ] then printf "%s\n" "NO" else printf "%s\n" "YES" printf " %s\n" "Trusted mode is on, but this hasn't been implemented in this script. Contact developer!" exit 1 fi } function check_nis { ## OBSOLETE : pwget/usermod also handles NIS # check if NIS is in use printf " %-60s" "Checking if NIS is used for authentication" /usr/bin/ypwhich 1>/dev/null 2>/dev/null if [ $? -eq 1 ] then NIS=0 printf "%s\n" "NO" else NIS=1 printf "%s\n" "YES" printf " %s\n" "NIS is in use, but this hasn't been implemented in this script. Contact developer!" exit 1 fi } function check_shadow { ## OBSOLETE : pwget/usermod also handles ShadowPassword # check if ShadowPassword is in use printf " %-60s" "Checking if passwords are shadow'ed" if [ -f /etc/shadow ] then SHADOW=1 printf "%s\n" "YES" else SHADOW=0 printf "%s\n" "NO" fi } function change_uid { # before any modification make a backup of the passwd/shadow file and keep the old value of the UID POSTFIX=$(date '+%d%m%Y_%H%M%S') OLDUID=$(pwget -n ${UNAME} | cut -d: -f3) if [ -f /etc/passwd ] then cp /etc/passwd /etc/passwd_${POSTFIX} fi if [ -f /etc/shadow ] then cp /etc/shadow /etc/shadow_${POSTFIX} fi # changing the UID with the usermod command printf " %-60s" "Changing the UID in the password database" CHECK_BEFORE="$(pwget | awk -F: '$1 == "'${UNAME}'" && $3 == "'${OLDUID}'"')" usermod -u ${UID} ${UNAME} 1>/dev/null 2>/dev/null # The following is an extended check because usermod fails in some strange cases if the permission of the homedir is 700 or so. # Only the password entry should be checked, the homedir can be ignored as the massive find/chown operation begins only after this. # That's why we cannot rely on the exit status of usermod. CHECK_AFTER="$(pwget | awk -F: '$1 == "'${UNAME}'" && $3 == "'${UID}'"')" if [[ $? -eq "0" || ! -z "${CHECK_AFTER}" ]] then printf "%s\n" "OK" printf " %-60s\n\n" "The UID was successfully changed:" printf " %-60s\n" "Before:" printf " %-60s\n" "${CHECK_BEFORE}" printf " %-60s\n" "After:" printf " %-60s\n" "${CHECK_AFTER}" print else printf "%s\n" "ERROR" printf " %-60s\n" "The change of the UID was failed, for more info try it manually:" printf " %-60s\n" "usermod -u ${UID} ${UNAME}" printf " %-60s\n" "...and do not forget to chown all the data to the new UID!" exit 7 fi } function change_ownership { # starting some find processes to change the ownership printf " %-60s\n" "Starting parallel processes to change ownership" # Now we start one find process on each separate NFS mount... for nfs in $(mount -v | awk '/ type nfs / {print $3}') do find ${nfs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} {} \+ & done & # only a single find process on the local disks... for localfs in $(mount -v | awk '$1 ~ /\/dev\/vg0[01]/ {print $3}') do find ${localfs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} \+ done & # ...and one find per FS on storage for storagefs in $(mount -v | awk '$1 !~ /\/dev\/vg0[01]/ && $5 !~ /nfs/ {print $3}') do find ${storagefs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} \+ & done & printf " %-60s\n\n" "Threads with chown: (the list may be incomplete on fast systems)" ps -ef | awk '(/chown/ || /find/) && ! /awk/' wait printf "\n %s\n" "FINISHED" } print UNAME=$1 UID=$2 check_root check_param ${UNAME} ${UID} change_uid change_ownership print
It needs some customization if you want to use it in your environment, because it assesses the type/location of the FS based on the name of the volumgroup. Feel free to comment & suggest modifications!
UPDATE 10.12.2010: Thanks to Markus, the testuser-bug was corrected, v0.4 released.