diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 29ee5e96b62..4f49e8a3b49 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -551,6 +551,9 @@ Release 2.1.0-beta - UNRELEASED HADOOP-9624. TestFSMainOperationsLocalFileSystem failed when the Hadoop test root path has "X" in its name. (Xi Fang via cnauroth) + HADOOP-9439. JniBasedUnixGroupsMapping: fix some crash bugs (Colin Patrick + McCabe) + BREAKDOWN OF HADOOP-8562 SUBTASKS AND RELATED JIRAS HADOOP-8924. Hadoop Common creating package-info.java must not depend on diff --git a/hadoop-common-project/hadoop-common/src/CMakeLists.txt b/hadoop-common-project/hadoop-common/src/CMakeLists.txt index e008e789084..bf8ac7be23c 100644 --- a/hadoop-common-project/hadoop-common/src/CMakeLists.txt +++ b/hadoop-common-project/hadoop-common/src/CMakeLists.txt @@ -180,7 +180,8 @@ add_dual_library(hadoop ${D}/net/unix/DomainSocket.c ${D}/security/JniBasedUnixGroupsMapping.c ${D}/security/JniBasedUnixGroupsNetgroupMapping.c - ${D}/security/getGroup.c + ${D}/security/hadoop_group_info.c + ${D}/security/hadoop_user_info.c ${D}/util/NativeCodeLoader.c ${D}/util/NativeCrc32.c ${D}/util/bulk_crc32.c diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java index 2c49a5de436..5e9fe5be322 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java @@ -98,7 +98,7 @@ public class NativeIO { static final String WORKAROUND_NON_THREADSAFE_CALLS_KEY = "hadoop.workaround.non.threadsafe.getpwuid"; - static final boolean WORKAROUND_NON_THREADSAFE_CALLS_DEFAULT = false; + static final boolean WORKAROUND_NON_THREADSAFE_CALLS_DEFAULT = true; private static long cacheTimeout = -1; diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/JniBasedUnixGroupsMapping.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/JniBasedUnixGroupsMapping.java index b711c589529..d397e44001c 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/JniBasedUnixGroupsMapping.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/JniBasedUnixGroupsMapping.java @@ -40,22 +40,44 @@ public class JniBasedUnixGroupsMapping implements GroupMappingServiceProvider { private static final Log LOG = LogFactory.getLog(JniBasedUnixGroupsMapping.class); - - native String[] getGroupForUser(String user); - + static { if (!NativeCodeLoader.isNativeCodeLoaded()) { throw new RuntimeException("Bailing out since native library couldn't " + "be loaded"); } + anchorNative(); LOG.debug("Using JniBasedUnixGroupsMapping for Group resolution"); } + /** + * Set up our JNI resources. + * + * @throws RuntimeException if setup fails. + */ + native static void anchorNative(); + + /** + * Get the set of groups associated with a user. + * + * @param username The user name + * + * @return The set of groups associated with a user. + */ + native static String[] getGroupsForUser(String username); + + /** + * Log an error message about a group. Used from JNI. + */ + static private void logError(int groupId, String error) { + LOG.error("error looking up the name of group " + groupId + ": " + error); + } + @Override public List getGroups(String user) throws IOException { String[] groups = new String[0]; try { - groups = getGroupForUser(user); + groups = getGroupsForUser(user); } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("Error getting groups for " + user, e); diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/exception.c b/hadoop-common-project/hadoop-common/src/main/native/src/exception.c index 39a03f9cde0..228af11bd8e 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/exception.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/exception.c @@ -107,3 +107,12 @@ jthrowable newIOException(JNIEnv* env, const char *fmt, ...) va_end(ap); return jthr; } + +const char* terror(int errnum) +{ + if ((errnum < 0) || (errnum >= sys_nerr)) { + return "unknown error."; + } + return sys_errlist[errnum]; +} + diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/exception.h b/hadoop-common-project/hadoop-common/src/main/native/src/exception.h index d7af3772c85..1ec47a653c8 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/exception.h +++ b/hadoop-common-project/hadoop-common/src/main/native/src/exception.h @@ -79,4 +79,12 @@ jthrowable newRuntimeException(JNIEnv* env, const char *fmt, ...) jthrowable newIOException(JNIEnv* env, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +/** + * Thread-safe strerror alternative. + * + * @param errnum Error number. + * @return Statically allocated error string. + */ +const char* terror(int errnum); + #endif diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c index d12abe036af..cb21a7bee66 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c @@ -59,7 +59,7 @@ static jmethodID nioe_ctor; // the monitor used for working around non-threadsafe implementations // of getpwuid_r, observed on platforms including RHEL 6.0. // Please see HADOOP-7156 for details. -static jobject pw_lock_object; +jobject pw_lock_object; // Internal functions static void throw_ioe(JNIEnv* env, int errnum); diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/net/unix/DomainSocket.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/net/unix/DomainSocket.c index 4502a1fd2af..26423f8d836 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/net/unix/DomainSocket.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/net/unix/DomainSocket.c @@ -107,14 +107,6 @@ static jthrowable newSocketException(JNIEnv *env, int errnum, return jthr; } -static const char* terror(int errnum) -{ - if ((errnum < 0) || (errnum >= sys_nerr)) { - return "unknown error."; - } - return sys_errlist[errnum]; -} - /** * Flexible buffer that will try to fit data on the stack, and fall back * to the heap if necessary. diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMapping.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMapping.c index 4b822d7737e..55adb59bb58 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMapping.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMapping.c @@ -30,88 +30,173 @@ #include #include +#include "exception.h" #include "org_apache_hadoop_security_JniBasedUnixGroupsMapping.h" #include "org_apache_hadoop.h" +#include "hadoop_group_info.h" +#include "hadoop_user_info.h" -static jobjectArray emptyGroups = NULL; +static jmethodID g_log_error_method; -JNIEXPORT jobjectArray JNICALL -Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_getGroupForUser -(JNIEnv *env, jobject jobj, jstring juser) { - extern int getGroupIDList(const char *user, int *ngroups, gid_t **groups); - extern int getGroupDetails(gid_t group, char **grpBuf); - const char *cuser = NULL; - jobjectArray jgroups = NULL; - int error = -1; +static jclass g_string_clazz; - if (emptyGroups == NULL) { - jobjectArray lEmptyGroups = (jobjectArray)(*env)->NewObjectArray(env, 0, - (*env)->FindClass(env, "java/lang/String"), NULL); - if (lEmptyGroups == NULL) { - goto cleanup; - } - emptyGroups = (*env)->NewGlobalRef(env, lEmptyGroups); - if (emptyGroups == NULL) { - goto cleanup; - } - } - char *grpBuf = NULL; - cuser = (*env)->GetStringUTFChars(env, juser, NULL); - if (cuser == NULL) { - goto cleanup; - } +extern jobject pw_lock_object; - /*Get the number of the groups, and their IDs, this user belongs to*/ - gid_t *groups = NULL; - int ngroups = 0; - error = getGroupIDList(cuser, &ngroups, &groups); - if (error != 0) { - goto cleanup; - } +JNIEXPORT void JNICALL +Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_anchorNative( +JNIEnv *env, jclass clazz) +{ + jobject string_clazz; - jgroups = (jobjectArray)(*env)->NewObjectArray(env, ngroups, - (*env)->FindClass(env, "java/lang/String"), NULL); - if (jgroups == NULL) { - error = -1; - goto cleanup; + g_log_error_method = (*env)->GetStaticMethodID(env, clazz, "logError", + "(ILjava/lang/String;)V"); + if (!g_log_error_method) { + return; // an exception has been raised } - - /*Iterate over the groupIDs and get the group structure for each*/ - int i = 0; - for (i = 0; i < ngroups; i++) { - error = getGroupDetails(groups[i],&grpBuf); - if (error != 0) { - goto cleanup; - } - jstring jgrp = (*env)->NewStringUTF(env, ((struct group*)grpBuf)->gr_name); - if (jgrp == NULL) { - error = -1; - goto cleanup; - } - (*env)->SetObjectArrayElement(env, jgroups,i,jgrp); - free(grpBuf); - grpBuf = NULL; + string_clazz = (*env)->FindClass(env, "java/lang/String"); + if (!string_clazz) { + return; // an exception has been raised } - -cleanup: - if (error == ENOMEM) { - THROW(env, "java/lang/OutOfMemoryError", NULL); - } - if (error == ENOENT) { - THROW(env, "java/io/IOException", "No entry for user"); - } - if (groups != NULL) { - free(groups); - } - if (grpBuf != NULL) { - free(grpBuf); - } - if (cuser != NULL) { - (*env)->ReleaseStringUTFChars(env, juser, cuser); - } - if (error == 0) { - return jgroups; - } else { - return emptyGroups; + g_string_clazz = (*env)->NewGlobalRef(env, string_clazz); + if (!g_string_clazz) { + jthrowable jthr = newRuntimeException(env, + "JniBasedUnixGroupsMapping#anchorNative: failed to make " + "a global reference to the java.lang.String class\n"); + (*env)->Throw(env, jthr); + return; } } + +/** + * Log an error about a failure to look up a group ID + * + * @param env The JNI environment + * @param clazz JniBasedUnixGroupsMapping class + * @param gid The gid we failed to look up + * @param ret Failure code + */ +static void logError(JNIEnv *env, jclass clazz, jint gid, int ret) +{ + jstring error_msg; + + error_msg = (*env)->NewStringUTF(env, terror(ret)); + if (!error_msg) { + (*env)->ExceptionClear(env); + return; + } + (*env)->CallStaticVoidMethod(env, clazz, g_log_error_method, gid, error_msg); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + return; + } + (*env)->DeleteLocalRef(env, error_msg); +} + +JNIEXPORT jobjectArray JNICALL +Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_getGroupsForUser +(JNIEnv *env, jclass clazz, jstring jusername) +{ + const char *username = NULL; + struct hadoop_user_info *uinfo = NULL; + struct hadoop_group_info *ginfo = NULL; + jstring jgroupname = NULL; + int i, ret, nvalid; + int pw_lock_locked = 0; + jobjectArray jgroups = NULL, jnewgroups = NULL; + + if (pw_lock_object != NULL) { + if ((*env)->MonitorEnter(env, pw_lock_object) != JNI_OK) { + goto done; // exception thrown + } + pw_lock_locked = 1; + } + username = (*env)->GetStringUTFChars(env, jusername, NULL); + if (username == NULL) { + goto done; // exception thrown + } + uinfo = hadoop_user_info_alloc(); + if (!uinfo) { + THROW(env, "java/lang/OutOfMemoryError", NULL); + goto done; + } + ret = hadoop_user_info_fetch(uinfo, username); + if (ret == ENOENT) { + jgroups = (*env)->NewObjectArray(env, 0, g_string_clazz, NULL); + goto done; + } + ginfo = hadoop_group_info_alloc(); + if (!ginfo) { + THROW(env, "java/lang/OutOfMemoryError", NULL); + goto done; + } + ret = hadoop_user_info_getgroups(uinfo); + if (ret) { + if (ret == ENOMEM) { + THROW(env, "java/lang/OutOfMemoryError", NULL); + } else { + char buf[128]; + snprintf(buf, sizeof(buf), "getgrouplist error %d (%s)", + ret, terror(ret)); + THROW(env, "java/lang/RuntimeException", buf); + } + goto done; + } + jgroups = (jobjectArray)(*env)->NewObjectArray(env, uinfo->num_gids, + g_string_clazz, NULL); + for (nvalid = 0, i = 0; i < uinfo->num_gids; i++) { + ret = hadoop_group_info_fetch(ginfo, uinfo->gids[i]); + if (ret) { + logError(env, clazz, uinfo->gids[i], ret); + } else { + jgroupname = (*env)->NewStringUTF(env, ginfo->group.gr_name); + if (!jgroupname) { // exception raised + (*env)->DeleteLocalRef(env, jgroups); + jgroups = NULL; + goto done; + } + (*env)->SetObjectArrayElement(env, jgroups, nvalid++, jgroupname); + // We delete the local reference once the element is in the array. + // This is OK because the array has a reference to it. + // Technically JNI only mandates that the JVM allow up to 16 local + // references at a time (though many JVMs allow more than that.) + (*env)->DeleteLocalRef(env, jgroupname); + } + } + if (nvalid != uinfo->num_gids) { + // If some group names could not be looked up, allocate a smaller array + // with just the entries that could be resolved. Java has no equivalent to + // realloc, so we have to do this manually. + jnewgroups = (jobjectArray)(*env)->NewObjectArray(env, nvalid, + (*env)->FindClass(env, "java/lang/String"), NULL); + if (!jnewgroups) { // exception raised + (*env)->DeleteLocalRef(env, jgroups); + jgroups = NULL; + goto done; + } + for (i = 0; i < nvalid; i++) { + jgroupname = (*env)->GetObjectArrayElement(env, jgroups, i); + (*env)->SetObjectArrayElement(env, jnewgroups, i, jgroupname); + (*env)->DeleteLocalRef(env, jgroupname); + } + (*env)->DeleteLocalRef(env, jgroups); + jgroups = jnewgroups; + } + +done: + if (pw_lock_locked) { + (*env)->MonitorExit(env, pw_lock_object); + } + if (username) { + (*env)->ReleaseStringUTFChars(env, jusername, username); + } + if (uinfo) { + hadoop_user_info_free(uinfo); + } + if (ginfo) { + hadoop_group_info_free(ginfo); + } + if (jgroupname) { + (*env)->DeleteLocalRef(env, jgroupname); + } + return jgroups; +} diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMappingWin.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMappingWin.c index 64d0fca6a96..28148d09c96 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMappingWin.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/JniBasedUnixGroupsMappingWin.c @@ -25,6 +25,13 @@ static jobjectArray emptyGroups = NULL; +JNIEXPORT void JNICALL +Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_anchorNative( +JNIEnv *env, jclass clazz) +{ + // no-op until full port of HADOOP-9439 to Windows is available +} + /* * Throw a java.IO.IOException, generating the message from errno. */ @@ -57,8 +64,8 @@ static void throw_ioexception(JNIEnv* env, DWORD errnum) } JNIEXPORT jobjectArray JNICALL -Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_getGroupForUser -(JNIEnv *env, jobject jobj, jstring juser) { +Java_org_apache_hadoop_security_JniBasedUnixGroupsMapping_getGroupsForUser +(JNIEnv *env, jclass clazz, jstring juser) { const WCHAR *user = NULL; jobjectArray jgroups = NULL; DWORD dwRtnCode = ERROR_SUCCESS; diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/getGroup.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/getGroup.c deleted file mode 100644 index f19ec79e16b..00000000000 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/getGroup.c +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include -#include -#include - -/*Helper functions for the JNI implementation of unix group mapping service*/ - - -/** - * Gets the group IDs for a given user. The groups argument is allocated - * internally, and it contains the list of groups. The ngroups is updated to - * the number of groups - * Returns 0 on success (on success, the caller must free the memory allocated - * internally) - */ -int getGroupIDList(const char *user, int *ngroups, gid_t **groups) { - int getPW(const char *user, char **pwbuf); - *ngroups = 0; - char *pwbuf = NULL; - *groups = NULL; - /*Look up the password database first*/ - int error = getPW(user, &pwbuf); - if (error != 0) { - if (pwbuf != NULL) { - free(pwbuf); - } - return error; - } - struct passwd *pw = (struct passwd*)pwbuf; - int ng = 0; - /*Get the groupIDs that this user belongs to*/ - if (getgrouplist(user, pw->pw_gid, NULL, &ng) < 0) { - *ngroups = ng; - *groups = (gid_t *) malloc(ng * sizeof (gid_t)); - if (!*groups) { - *ngroups = 0; - free(pwbuf); - return ENOMEM; - } - if (getgrouplist(user, pw->pw_gid, *groups, &ng) < 0) { - *ngroups = 0; - free(pwbuf); - free(*groups); - *groups = NULL; - return ENOENT; - } - } - free(pwbuf); - return 0; -} - -/** - * Gets the group structure for a given group ID. - * The grpBuf argument is allocated internally and it contains the - * struct group for the given group ID. - * Returns 0 on success (on success, the caller must free the memory allocated - * internally) - */ -int getGroupDetails(gid_t group, char **grpBuf) { - struct group * grp = NULL; - long currBufferSize = sysconf(_SC_GETGR_R_SIZE_MAX); - if (currBufferSize < 1024) { - currBufferSize = 1024; - } - *grpBuf = NULL; - char *buf = (char*)malloc(sizeof(char) * currBufferSize); - - if (!buf) { - return ENOMEM; - } - int error; - for (;;) { - error = getgrgid_r(group, (struct group*)buf, - buf + sizeof(struct group), - currBufferSize - sizeof(struct group), &grp); - if(error != ERANGE) { - break; - } - free(buf); - currBufferSize *= 2; - buf = malloc(sizeof(char) * currBufferSize); - if(!buf) { - return ENOMEM; - } - } - if(!grp && !error) { - free(buf); - return ENOENT; - } else if (error) { - free(buf); - return error; - } - *grpBuf = buf; - return 0; -} - -/** - * Gets the password database entry for a given user. - * The pwbuf argument is allocated internally and it contains the - * broken out fields for the password database entry - * Returns 0 on success (on success, the caller must free the memory allocated - * internally). - */ -int getPW(const char *user, char **pwbuf) { - struct passwd *pwbufp = NULL; - long currBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (currBufferSize < 1024) { - currBufferSize = 1024; - } - *pwbuf = NULL; - char *buf = (char*)malloc(sizeof(char) * currBufferSize); - - if (!buf) { - return ENOMEM; - } - int error; - - for (;;) { - error = getpwnam_r(user, (struct passwd*)buf, buf + sizeof(struct passwd), - currBufferSize - sizeof(struct passwd), &pwbufp); - if (error != ERANGE) { - break; - } - free(buf); - currBufferSize *= 2; - buf = (char*)malloc(sizeof(char) * currBufferSize); - if (!buf) { - return ENOMEM; - } - } - if (!pwbufp && !error) { - free(buf); - return ENOENT; - } else if (error) { - free(buf); - return error; - } - *pwbuf = buf; - return 0; -} - -#undef TESTING - -#ifdef TESTING -/** - * A main() is provided so that quick testing of this - * library can be done. - */ -int main(int argc, char **argv) { - int ngroups; - gid_t *groups = NULL; - char *user = "ddas"; - if (argc == 2) user = argv[1]; - int error = getGroupIDList(user, &ngroups, &groups); - if (error != 0) { - printf("Couldn't obtain grp for user %s", user); - return; - } - int i; - for (i = 0; i < ngroups; i++) { - char *grpbuf = NULL; - error = getGroupDetails(groups[i], &grpbuf); - printf("grps[%d]: %s ",i, ((struct group*)grpbuf)->gr_name); - free(grpbuf); - } - free(groups); - return 0; -} -#endif diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.c new file mode 100644 index 00000000000..5d269fe38d1 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.c @@ -0,0 +1,148 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hadoop_group_info.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct hadoop_group_info *hadoop_group_info_alloc(void) +{ + struct hadoop_group_info *ginfo; + size_t buf_sz; + char *buf; + + ginfo = calloc(1, sizeof(struct hadoop_group_info)); + buf_sz = sysconf(_SC_GETGR_R_SIZE_MAX); + if (buf_sz < 1024) { + buf_sz = 1024; + } + buf = malloc(buf_sz); + if (!buf) { + free(ginfo); + return NULL; + } + ginfo->buf_sz = buf_sz; + ginfo->buf = buf; + return ginfo; +} + +void hadoop_group_info_clear(struct hadoop_group_info *ginfo) +{ + struct group *group = &ginfo->group; + + group->gr_name = NULL; + group->gr_passwd = NULL; + group->gr_gid = 0; + group->gr_mem = NULL; +} + +void hadoop_group_info_free(struct hadoop_group_info *ginfo) +{ + free(ginfo->buf); + free(ginfo); +} + +/** + * Different platforms use different error codes to represent "group not found." + * So whitelist the errors which do _not_ mean "group not found." + * + * @param err The errno + * + * @return The error code to use + */ +static int getgrgid_error_translate(int err) +{ + if ((err == EIO) || (err == EMFILE) || (err == ENFILE) || + (err == ENOMEM) || (err == ERANGE)) { + return err; + } + return ENOENT; +} + +int hadoop_group_info_fetch(struct hadoop_group_info *ginfo, gid_t gid) +{ + struct group *group; + int err; + size_t buf_sz; + char *nbuf; + + hadoop_group_info_clear(ginfo); + for (;;) { + do { + group = NULL; + err = getgrgid_r(gid, &ginfo->group, ginfo->buf, + ginfo->buf_sz, &group); + } while ((!group) && (err == EINTR)); + if (group) { + return 0; + } + if (err != ERANGE) { + return getgrgid_error_translate(errno); + } + buf_sz = ginfo->buf_sz * 2; + nbuf = realloc(ginfo->buf, buf_sz); + if (!nbuf) { + return ENOMEM; + } + ginfo->buf = nbuf; + ginfo->buf_sz = buf_sz; + } +} + +#ifdef GROUP_TESTING +/** + * A main() is provided so that quick testing of this + * library can be done. + */ +int main(int argc, char **argv) { + char **groupname; + struct hadoop_group_info *ginfo; + int ret; + + ginfo = hadoop_group_info_alloc(); + if (!ginfo) { + fprintf(stderr, "hadoop_group_info_alloc returned NULL.\n"); + return EXIT_FAILURE; + } + for (groupname = argv + 1; *groupname; groupname++) { + gid_t gid = atoi(*groupname); + if (gid == 0) { + fprintf(stderr, "won't accept non-parseable group-name or gid 0: %s\n", + *groupname); + return EXIT_FAILURE; + } + ret = hadoop_group_info_fetch(ginfo, gid); + if (!ret) { + fprintf(stderr, "gid[%lld] : gr_name = %s\n", + (long long)gid, ginfo->group.gr_name); + } else { + fprintf(stderr, "group[%lld] : error %d (%s)\n", + (long long)gid, ret, strerror(ret)); + } + } + hadoop_group_info_free(ginfo); + return EXIT_SUCCESS; +} +#endif diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.h b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.h new file mode 100644 index 00000000000..6547d8a5f94 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_group_info.h @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HADOOP_GROUP_INFO_DOT_H +#define HADOOP_GROUP_INFO_DOT_H + +#include /* for struct group */ +#include /* for size_t */ + +struct hadoop_group_info { + size_t buf_sz; + struct group group; + char *buf; +}; + +/** + * Allocate a hadoop group info context. + * + * @return NULL on OOM; the context otherwise. + */ +struct hadoop_group_info *hadoop_group_info_alloc(void); + +/** + * Free a hadoop group info context. + * + * @param uinfo The hadoop group info context to free. + */ +void hadoop_group_info_free(struct hadoop_group_info *ginfo); + +/** + * Look up information for a group id. + * + * @param uinfo The hadoop group info context. + * Existing data in this context will be cleared. + * @param gid The group id to look up. + * + * @return ENOENT if the group wasn't found; + * 0 on success; + * EIO, EMFILE, ENFILE, or ENOMEM if appropriate. + */ +int hadoop_group_info_fetch(struct hadoop_group_info *ginfo, gid_t gid); + +#endif diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.c new file mode 100644 index 00000000000..2b11032e1c3 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.c @@ -0,0 +1,203 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hadoop_user_info.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define INITIAL_GIDS_SIZE 32 + +struct hadoop_user_info *hadoop_user_info_alloc(void) +{ + struct hadoop_user_info *uinfo; + size_t buf_sz; + char *buf; + + uinfo = calloc(1, sizeof(struct hadoop_user_info)); + buf_sz = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buf_sz < 1024) { + buf_sz = 1024; + } + buf = malloc(buf_sz); + if (!buf) { + free(uinfo); + return NULL; + } + uinfo->buf_sz = buf_sz; + uinfo->buf = buf; + return uinfo; +} + +static void hadoop_user_info_clear(struct hadoop_user_info *uinfo) +{ + struct passwd *pwd = &uinfo->pwd; + + pwd->pw_name = NULL; + pwd->pw_uid = 0; + pwd->pw_gid = 0; + pwd->pw_passwd = NULL; + pwd->pw_gecos = NULL; + pwd->pw_dir = NULL; + pwd->pw_shell = NULL; + free(uinfo->gids); + uinfo->gids = 0; + uinfo->num_gids = 0; + uinfo->gids_size = 0; +} + +void hadoop_user_info_free(struct hadoop_user_info *uinfo) +{ + free(uinfo->buf); + hadoop_user_info_clear(uinfo); + free(uinfo); +} + +/** + * Different platforms use different error codes to represent "user not found." + * So whitelist the errors which do _not_ mean "user not found." + * + * @param err The errno + * + * @return The error code to use + */ +static int getpwnam_error_translate(int err) +{ + if ((err == EIO) || (err == EMFILE) || (err == ENFILE) || + (err == ENOMEM) || (err == ERANGE)) { + return err; + } + return ENOENT; +} + +int hadoop_user_info_fetch(struct hadoop_user_info *uinfo, + const char *username) +{ + struct passwd *pwd; + int err; + size_t buf_sz; + char *nbuf; + + hadoop_user_info_clear(uinfo); + for (;;) { + do { + pwd = NULL; + err = getpwnam_r(username, &uinfo->pwd, uinfo->buf, + uinfo->buf_sz, &pwd); + } while ((!pwd) && (errno == EINTR)); + if (pwd) { + return 0; + } + if (err != ERANGE) { + return getpwnam_error_translate(errno); + } + buf_sz = uinfo->buf_sz * 2; + nbuf = realloc(uinfo->buf, buf_sz); + if (!nbuf) { + return ENOMEM; + } + uinfo->buf = nbuf; + uinfo->buf_sz = buf_sz; + } +} + +int hadoop_user_info_getgroups(struct hadoop_user_info *uinfo) +{ + int ret, ngroups; + gid_t *ngids; + + if (!uinfo->pwd.pw_name) { + return EINVAL; // invalid user info + } + uinfo->num_gids = 0; + if (!uinfo->gids) { + uinfo->gids = malloc(sizeof(uinfo->gids[0]) * INITIAL_GIDS_SIZE); + if (!uinfo->gids) { + return ENOMEM; + } + uinfo->gids_size = INITIAL_GIDS_SIZE; + } + ngroups = uinfo->gids_size; + ret = getgrouplist(uinfo->pwd.pw_name, uinfo->pwd.pw_gid, + uinfo->gids, &ngroups); + if (ret != -1) { + uinfo->num_gids = ngroups; + return 0; + } + ngids = realloc(uinfo->gids, sizeof(uinfo->gids[0]) * ngroups); + if (!ngids) { + return ENOMEM; + } + uinfo->gids = ngids; + uinfo->gids_size = ngroups; + ret = getgrouplist(uinfo->pwd.pw_name, uinfo->pwd.pw_gid, + uinfo->gids, &ngroups); + if (ret != -1) { + uinfo->num_gids = ngroups; + return 0; + } + return EIO; +} + +#ifdef USER_TESTING +/** + * A main() is provided so that quick testing of this + * library can be done. + */ +int main(int argc, char **argv) { + char **username, *prefix; + struct hadoop_user_info *uinfo; + int i, ret; + + uinfo = hadoop_user_info_alloc(); + if (!uinfo) { + fprintf(stderr, "hadoop_user_info_alloc returned NULL.\n"); + return EXIT_FAILURE; + } + for (username = argv + 1; *username; username++) { + ret = hadoop_user_info_fetch(uinfo, *username); + if (!ret) { + fprintf(stderr, "user[%s] : pw_uid = %lld\n", + *username, (long long)uinfo->pwd.pw_uid); + } else { + fprintf(stderr, "user[%s] : error %d (%s)\n", + *username, ret, strerror(ret)); + } + ret = hadoop_user_info_getgroups(uinfo); + if (!ret) { + fprintf(stderr, " getgroups: "); + prefix = ""; + for (i = 0; i < uinfo->num_gids; i++) { + fprintf(stderr, "%s%lld", prefix, (long long)uinfo->gids[i]); + prefix = ", "; + } + fprintf(stderr, "\n"); + } else { + fprintf(stderr, " getgroups: error %d\n", ret); + } + } + hadoop_user_info_free(uinfo); + return EXIT_SUCCESS; +} +#endif diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.h b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.h new file mode 100644 index 00000000000..7e71c0187fd --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/security/hadoop_user_info.h @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HADOOP_USER_INFO_DOT_H +#define HADOOP_USER_INFO_DOT_H + +#include /* for struct passwd */ +#include /* for size_t */ + +struct hadoop_user_info { + size_t buf_sz; + struct passwd pwd; + char *buf; + gid_t *gids; + int num_gids; + int gids_size; +}; + +/** + * Allocate a hadoop user info context. + * + * @return NULL on OOM; the context otherwise. + */ +struct hadoop_user_info *hadoop_user_info_alloc(void); + +/** + * Free a hadoop user info context. + * + * @param uinfo The hadoop user info context to free. + */ +void hadoop_user_info_free(struct hadoop_user_info *uinfo); + +/** + * Look up information for a user name. + * + * @param uinfo The hadoop user info context. + * Existing data in this context will be cleared. + * @param username The user name to look up. + * + * @return ENOENT if the user wasn't found; + * 0 on success; + * EIO, EMFILE, ENFILE, or ENOMEM if appropriate. + */ +int hadoop_user_info_fetch(struct hadoop_user_info *uinfo, + const char *username); + +/** + * Look up the groups this user belongs to. + * + * @param uinfo The hadoop user info context. + * uinfo->gids will be filled in on a successful + * return; + * + * @return 0 on success. + * ENOMEM if we ran out of memory. + * EINVAL if the uinfo was invalid. + */ +int hadoop_user_info_getgroups(struct hadoop_user_info *uinfo); + +#endif