HADOOP-9439. JniBasedUnixGroupsMapping: fix some crash bugs (Colin Patrick McCabe)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-2@1496115 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Colin McCabe 2013-06-24 16:25:52 +00:00
parent d4980decb4
commit 16dafcf5c3
15 changed files with 698 additions and 280 deletions

View File

@ -285,6 +285,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

View File

@ -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

View File

@ -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;

View File

@ -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<String> 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);

View File

@ -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];
}

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -30,88 +30,173 @@
#include <pwd.h>
#include <string.h>
#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;
}

View File

@ -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;

View File

@ -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 <grp.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
/*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

View File

@ -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 <errno.h>
#include <grp.h>
#include <pthread.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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

View File

@ -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 <grp.h> /* for struct group */
#include <unistd.h> /* 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

View File

@ -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 <errno.h>
#include <grp.h>
#include <pthread.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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

View File

@ -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 <pwd.h> /* for struct passwd */
#include <unistd.h> /* 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