From: Jackie Huang <[email protected]> A race condition can occur when adding users and groups to the passwd and group files, in [YOCTO #1794], 10 times retry added but it is not fixed completely.
This fix re-writes the useradd_preinst and useradd_sysroot with python and use locking of bb.utils to lock the passwd and group files before executing useradd/groupadd commands to avoid the lock race themselves. [YOCTO #2779] Signed-off-by: Jackie Huang <[email protected]> --- meta/classes/useradd.bbclass | 284 ++++++++++++++++++------------------------ 1 files changed, 124 insertions(+), 160 deletions(-) diff --git a/meta/classes/useradd.bbclass b/meta/classes/useradd.bbclass index bb8f42b..ed5ed69 100644 --- a/meta/classes/useradd.bbclass +++ b/meta/classes/useradd.bbclass @@ -14,126 +14,90 @@ USERADDDEPENDS_virtclass-nativesdk = "" # c) As the preinst script in the target package at do_rootfs time # d) As the preinst script in the target package on device as a package upgrade # -useradd_preinst () { -OPT="" -SYSROOT="" - -if test "x$D" != "x"; then - # Installing into a sysroot - SYSROOT="$D" - OPT="--root $D" - - # Add groups and users defined for all recipe packages - GROUPADD_PARAM="${@get_all_cmd_params(d, 'group')}" - USERADD_PARAM="${@get_all_cmd_params(d, 'user')}" -else - # Installing onto a target - # Add groups and users defined only for this package - GROUPADD_PARAM="${GROUPADD_PARAM}" - USERADD_PARAM="${USERADD_PARAM}" -fi - -# Perform group additions first, since user additions may depend -# on these groups existing -if test "x$GROUPADD_PARAM" != "x"; then - echo "Running groupadd commands..." - # Invoke multiple instances of groupadd for parameter lists - # separated by ';' - opts=`echo "$GROUPADD_PARAM" | cut -d ';' -f 1` - remaining=`echo "$GROUPADD_PARAM" | cut -d ';' -f 2-` - while test "x$opts" != "x"; do - groupname=`echo "$opts" | awk '{ print $NF }'` - group_exists=`grep "^$groupname:" $SYSROOT/etc/group || true` - if test "x$group_exists" = "x"; then - count=1 - while true; do - eval $PSEUDO groupadd $OPT $opts || true - group_exists=`grep "^$groupname:" $SYSROOT/etc/group || true` - if test "x$group_exists" = "x"; then - # File locking issues can require us to retry the command - echo "WARNING: groupadd command did not succeed. Retrying..." - sleep 1 - else - break - fi - count=`expr $count + 1` - if test $count = 11; then - echo "ERROR: tried running groupadd command 10 times without success, giving up" - exit 1 - fi - done - else - echo "Note: group $groupname already exists, not re-creating it" - fi - - if test "x$opts" = "x$remaining"; then - break - fi - opts=`echo "$remaining" | cut -d ';' -f 1` - remaining=`echo "$remaining" | cut -d ';' -f 2-` - done -fi - -if test "x$USERADD_PARAM" != "x"; then - echo "Running useradd commands..." - # Invoke multiple instances of useradd for parameter lists - # separated by ';' - opts=`echo "$USERADD_PARAM" | cut -d ';' -f 1` - remaining=`echo "$USERADD_PARAM" | cut -d ';' -f 2-` - while test "x$opts" != "x"; do - # useradd does not have a -f option, so we have to check if the - # username already exists manually - username=`echo "$opts" | awk '{ print $NF }'` - user_exists=`grep "^$username:" $SYSROOT/etc/passwd || true` - if test "x$user_exists" = "x"; then - count=1 - while true; do - eval $PSEUDO useradd $OPT $opts || true - user_exists=`grep "^$username:" $SYSROOT/etc/passwd || true` - if test "x$user_exists" = "x"; then - # File locking issues can require us to retry the command - echo "WARNING: useradd command did not succeed. Retrying..." - sleep 1 - else - break - fi - count=`expr $count + 1` - if test $count = 11; then - echo "ERROR: tried running useradd command 10 times without success, giving up" - exit 1 - fi - done - else - echo "Note: username $username already exists, not re-creating it" - fi - - if test "x$opts" = "x$remaining"; then - break - fi - opts=`echo "$remaining" | cut -d ';' -f 1` - remaining=`echo "$remaining" | cut -d ';' -f 2-` - done -fi +def useradd_preinst(d): + import re + + sysroot = "" + opt = "" + + dir = d.getVar('STAGING_DIR_TARGET', True) + if dir: + # Installing into a sysroot + sysroot = dir + opt = "--root %s" % dir + + # Add groups and users defined for all recipe packages + groupadd_param = get_all_cmd_params(d, 'group') + useradd_param = get_all_cmd_params(d, 'user') + else: + # Installing onto a target + # Add groups and users defined only for this package + groupadd_param = d.getVar('GROUPADD_PARAM', True) + useradd_param = d.getVar('GROUPADD_PARAM', True) + + group_file = '%s/etc/group' % sysroot + user_file = '%s/etc/passwd' % sysroot + + # Use the locking of bb to the group/passwd file to avoid the + # locking issue of groupadd/useradd + group_lock = '%s.locked' % group_file + user_lock = '%s.locked' % user_file + lockfiles = [group_lock, user_lock] + + with bb.utils.fileslocked(lockfiles): + # Perform group additions first, since user additions may depend + # on these groups existing + if groupadd_param and sysroot: + bb.debug(1, "Running groupadd commands ...") + # Invoke multiple instances of groupadd for parameter lists + # separated by ';' + param_list = groupadd_param.split(';') + for opts in param_list: + groupname = opts.split()[-1] + with open(group_file, 'r') as f: + passwd_lines = f.read() + group_re = re.compile('\n%s' % groupname) + if group_re.search(passwd_lines): + bb.note("Note: groupname %s already exists, not re-creating it" % groupname) + continue + try: + output, err = bb.process.run('groupadd %s %s' % (opt, opts)) + except bb.process.CmdError as exc: + bb.error("Failed to add group: %s" % exc) + else: + bb.note("Successful to add group %s" % groupname) + + if useradd_param: + bb.debug(1, "Running useradd commands ...") + # Invoke multiple instances of useradd for parameter lists + # separated by ';' + param_list = useradd_param.split(';') + for opts in param_list: + # useradd does not have a -f option, so we have to check if the + # username already exists manually + username = opts.split()[-1] + with open(user_file, 'r') as f: + passwd_lines = f.read() + user_re = re.compile('\n%s' % username) + if user_re.search(passwd_lines): + bb.note("Note: username %s already exists, not re-creating it" % username) + continue + try: + output, err = bb.process.run('useradd %s %s' % (opt, opts)) + except bb.process.CmdError as exc: + bb.error("Failed to add user: %s" % exc) + else: + bb.note("Successful to add user %s" % username) + +fakeroot python useradd_sysroot () { + useradd_preinst(d) } -useradd_sysroot () { - # Pseudo may (do_install) or may not (do_populate_sysroot_setscene) be running - # at this point so we're explicit about the environment so pseudo can load if - # not already present. - export PSEUDO="${FAKEROOTENV} PSEUDO_LOCALSTATEDIR=${STAGING_DIR_TARGET}${localstatedir}/pseudo ${STAGING_DIR_NATIVE}${bindir}/pseudo" - - # Explicitly set $D since it isn't set to anything - # before do_install - D=${STAGING_DIR_TARGET} - useradd_preinst +fakeroot python useradd_sysroot_sstate () { + if d.getVar("BB_CURRENTTASK", True) == "package_setscene": + useradd_preinst(d) } -useradd_sysroot_sstate () { - if [ "${BB_CURRENTTASK}" = "package_setscene" ] - then - useradd_sysroot - fi -} do_install[prefuncs] += "${SYSROOTFUNC}" SYSROOTFUNC = "useradd_sysroot" @@ -146,7 +110,7 @@ SYSROOTPOSTFUNC_virtclass-cross = "" SYSROOTPOSTFUNC_virtclass-native = "" SYSROOTPOSTFUNC_virtclass-nativesdk = "" -USERADDSETSCENEDEPS = "${MLPREFIX}base-passwd:do_populate_sysroot_setscene shadow-native:do_populate_sysroot_setscene ${MLPREFIX}shadow-sysroot:do_populate_sysroot_setscene" +USERADDSETSCENEDEPS = "base-passwd:do_populate_sysroot_setscene shadow-native:do_populate_sysroot_setscene ${MLPREFIX}shadow-sysroot:do_populate_sysroot_setscene" USERADDSETSCENEDEPS_virtclass-cross = "" USERADDSETSCENEDEPS_virtclass-native = "" USERADDSETSCENEDEPS_virtclass-nativesdk = "" @@ -154,61 +118,61 @@ do_package_setscene[depends] = "${USERADDSETSCENEDEPS}" # Recipe parse-time sanity checks def update_useradd_after_parse(d): - useradd_packages = d.getVar('USERADD_PACKAGES', True) + useradd_packages = d.getVar('USERADD_PACKAGES', True) - if not useradd_packages: - raise bb.build.FuncFailed, "%s inherits useradd but doesn't set USERADD_PACKAGES" % d.getVar('FILE') + if not useradd_packages: + raise bb.build.FuncFailed, "%s inherits useradd but doesn't set USERADD_PACKAGES" % d.getVar('FILE') - for pkg in useradd_packages.split(): - if not d.getVar('USERADD_PARAM_%s' % pkg, True) and not d.getVar('GROUPADD_PARAM_%s' % pkg, True): - raise bb.build.FuncFailed, "%s inherits useradd but doesn't set USERADD_PARAM or GROUPADD_PARAM for package %s" % (d.getVar('FILE'), pkg) + for pkg in useradd_packages.split(): + if not d.getVar('USERADD_PARAM_%s' % pkg, True) and not d.getVar('GROUPADD_PARAM_%s' % pkg, True): + raise bb.build.FuncFailed, "%s inherits useradd but doesn't set USERADD_PARAM or GROUPADD_PARAM for package %s" % (d.getVar('FILE'), pkg) python __anonymous() { - update_useradd_after_parse(d) + update_useradd_after_parse(d) } # Return a single [GROUP|USER]ADD_PARAM formatted string which includes the # [group|user]add parameters for all USERADD_PACKAGES in this recipe def get_all_cmd_params(d, cmd_type): - import string - - param_type = cmd_type.upper() + "ADD_PARAM_%s" - params = [] + import string + + param_type = cmd_type.upper() + "ADD_PARAM_%s" + params = [] - useradd_packages = d.getVar('USERADD_PACKAGES', True) or "" - for pkg in useradd_packages.split(): - param = d.getVar(param_type % pkg, True) - if param: - params.append(param) + useradd_packages = d.getVar('USERADD_PACKAGES', True) or "" + for pkg in useradd_packages.split(): + param = d.getVar(param_type % pkg, True) + if param: + params.append(param) - return string.join(params, "; ") + return string.join(params, "; ") # Adds the preinst script into generated packages fakeroot python populate_packages_prepend () { - def update_useradd_package(pkg): - bb.debug(1, 'adding user/group calls to preinst for %s' % pkg) - - """ - useradd preinst is appended here because pkg_preinst may be - required to execute on the target. Not doing so may cause - useradd preinst to be invoked twice, causing unwanted warnings. - """ - preinst = d.getVar('pkg_preinst_%s' % pkg, True) or d.getVar('pkg_preinst', True) - if not preinst: - preinst = '#!/bin/sh\n' - preinst += d.getVar('useradd_preinst', True) - d.setVar('pkg_preinst_%s' % pkg, preinst) - - # RDEPENDS setup - rdepends = d.getVar("RDEPENDS_%s" % pkg, True) or "" - rdepends += ' ' + d.getVar('MLPREFIX') + 'base-passwd' - rdepends += ' ' + d.getVar('MLPREFIX') + 'shadow' - d.setVar("RDEPENDS_%s" % pkg, rdepends) - - # Add the user/group preinstall scripts and RDEPENDS requirements - # to packages specified by USERADD_PACKAGES - if not bb.data.inherits_class('nativesdk', d): - useradd_packages = d.getVar('USERADD_PACKAGES', True) or "" - for pkg in useradd_packages.split(): - update_useradd_package(pkg) + def update_useradd_package(pkg): + bb.debug(1, 'adding user/group calls to preinst for %s' % pkg) + + """ + useradd preinst is appended here because pkg_preinst may be + required to execute on the target. Not doing so may cause + useradd preinst to be invoked twice, causing unwanted warnings. + """ + preinst = d.getVar('pkg_preinst_%s' % pkg, True) or d.getVar('pkg_preinst', True) + if not preinst: + preinst = '#!/bin/sh\n' + preinst += d.getVar('useradd_preinst', True) + d.setVar('pkg_preinst_%s' % pkg, preinst) + + # RDEPENDS setup + rdepends = d.getVar("RDEPENDS_%s" % pkg, True) or "" + rdepends += ' ' + d.getVar('MLPREFIX') + 'base-passwd' + rdepends += ' ' + d.getVar('MLPREFIX') + 'shadow' + d.setVar("RDEPENDS_%s" % pkg, rdepends) + + # Add the user/group preinstall scripts and RDEPENDS requirements + # to packages specified by USERADD_PACKAGES + if not bb.data.inherits_class('nativesdk', d): + useradd_packages = d.getVar('USERADD_PACKAGES', True) or "" + for pkg in useradd_packages.split(): + update_useradd_package(pkg) } -- 1.7.4 _______________________________________________ Openembedded-core mailing list [email protected] http://lists.linuxtogo.org/cgi-bin/mailman/listinfo/openembedded-core
