blob: e2438b1b06c859447c10b6ad7af85bbc7f81415f [file] [log] [blame]
/**
* 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
// 1KB buffer should be large enough to store a passwd record in most
// cases, but it can get bigger if each field is maximally used. The
// max is defined to avoid buggy libraries making us run out of memory.
#define MAX_USER_BUFFER_SIZE (32*1024)
struct hadoop_user_info *hadoop_user_info_alloc(void)
{
struct hadoop_user_info *uinfo;
long 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 ret;
size_t buf_sz;
char *nbuf;
hadoop_user_info_clear(uinfo);
for (;;) {
// On success, the following call returns 0 and pwd is set to non-NULL.
pwd = NULL;
ret = getpwnam_r(username, &uinfo->pwd, uinfo->buf,
uinfo->buf_sz, &pwd);
switch(ret) {
case 0:
if (!pwd) {
// Not found.
return ENOENT;
}
// Found.
return 0;
case EINTR:
// EINTR: a signal was handled and this thread was allowed to continue.
break;
case ERANGE:
// ERANGE: the buffer was not big enough.
if (uinfo->buf_sz == MAX_USER_BUFFER_SIZE) {
// Already tried with the max size.
return ENOMEM;
}
buf_sz = uinfo->buf_sz * 2;
if (buf_sz > MAX_USER_BUFFER_SIZE) {
buf_sz = MAX_USER_BUFFER_SIZE;
}
nbuf = realloc(uinfo->buf, buf_sz);
if (!nbuf) {
return ENOMEM;
}
uinfo->buf = nbuf;
uinfo->buf_sz = buf_sz;
break;
default:
// Lookup failed.
return getpwnam_error_translate(ret);
}
}
}
static int put_primary_gid_first(struct hadoop_user_info *uinfo)
{
int i, num_gids = uinfo->num_gids;
gid_t first_gid;
gid_t gid;
gid_t primary = uinfo->pwd.pw_gid;
if (num_gids < 1) {
// There are no gids, but we expected at least one.
return EINVAL;
}
first_gid = uinfo->gids[0];
if (first_gid == primary) {
// First gid is already the primary.
return 0;
}
for (i = 1; i < num_gids; i++) {
gid = uinfo->gids[i];
if (gid == primary) {
// swap first gid and this gid.
uinfo->gids[0] = gid;
uinfo->gids[i] = first_gid;
return 0;
}
}
// Did not find the primary gid in the list.
return EINVAL;
}
int hadoop_user_info_getgroups(struct hadoop_user_info *uinfo)
{
int ret, ngroups;
gid_t *ngids;
if (!uinfo->pwd.pw_name) {
// invalid user info
return EINVAL;
}
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);
// Return value is different on Linux vs. FreeBSD. Linux: the number of groups
// or -1 on error. FreeBSD: 0 on success or -1 on error. Unfortunately, we
// can't accept a 0 return on Linux, because buggy implementations have been
// observed to return 0 but leave the other out parameters in an indeterminate
// state. This deviates from the man page, but it has been observed in
// practice. See issue HADOOP-10989 for details.
#ifdef __linux__
if (ret > 0) {
#else
if (ret >= 0) {
#endif
uinfo->num_gids = ngroups;
ret = put_primary_gid_first(uinfo);
if (ret) {
return ret;
}
return 0;
} else if (ret != -1) {
// Any return code that is not -1 is considered as error.
// Since the user lookup was successful, there should be at least one
// group for this user.
return EIO;
}
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 < 0) {
return EIO;
}
uinfo->num_gids = ngroups;
ret = put_primary_gid_first(uinfo);
return ret;
}
#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