| /** |
| * 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. |
| */ |
| package org.apache.hadoop.security; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.util.Time; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.BiMap; |
| import com.google.common.collect.HashBiMap; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A simple shell-based implementation of {@link IdMappingServiceProvider} |
| * Map id to user name or group name. It does update every 15 minutes. Only a |
| * single instance of this class is expected to be on the server. |
| * |
| * The maps are incrementally updated as described below: |
| * 1. Initialize the maps as empty. |
| * 2. Incrementally update the maps |
| * - When ShellBasedIdMapping is requested for user or group name given |
| * an ID, or for ID given a user or group name, do look up in the map |
| * first, if it doesn't exist, find the corresponding entry with shell |
| * command, and insert the entry to the maps. |
| * - When group ID is requested for a given group name, and if the |
| * group name is numerical, the full group map is loaded. Because we |
| * don't have a good way to find the entry for a numerical group name, |
| * loading the full map helps to get in all entries. |
| * 3. Periodically refresh the maps for both user and group, e.g, |
| * do step 1. |
| * Note: for testing purpose, step 1 may initial the maps with full mapping |
| * when using constructor |
| * {@link ShellBasedIdMapping#ShellBasedIdMapping(Configuration, boolean)}. |
| */ |
| public class ShellBasedIdMapping implements IdMappingServiceProvider { |
| |
| private static final Logger LOG = |
| LoggerFactory.getLogger(ShellBasedIdMapping.class); |
| |
| private final static String OS = System.getProperty("os.name"); |
| |
| /** Shell commands to get users and groups */ |
| static final String GET_ALL_USERS_CMD = "getent passwd | cut -d: -f1,3"; |
| static final String GET_ALL_GROUPS_CMD = "getent group | cut -d: -f1,3"; |
| static final String MAC_GET_ALL_USERS_CMD = "dscl . -list /Users UniqueID"; |
| static final String MAC_GET_ALL_GROUPS_CMD = "dscl . -list /Groups PrimaryGroupID"; |
| |
| private final File staticMappingFile; |
| private StaticMapping staticMapping = null; |
| // Last time the static map was modified, measured time difference in |
| // milliseconds since midnight, January 1, 1970 UTC |
| private long lastModificationTimeStaticMap = 0; |
| |
| private boolean constructFullMapAtInit = false; |
| |
| // Used for parsing the static mapping file. |
| private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$"); |
| private static final Pattern COMMENT_LINE = Pattern.compile("^\\s*#.*$"); |
| private static final Pattern MAPPING_LINE = |
| Pattern.compile("^(uid|gid)\\s+(\\d+)\\s+(0|-?[1-9]\\d*)\\s*(#.*)?$"); |
| |
| final private long timeout; |
| |
| // Maps for id to name map. Guarded by this object monitor lock |
| private BiMap<Integer, String> uidNameMap = HashBiMap.create(); |
| private BiMap<Integer, String> gidNameMap = HashBiMap.create(); |
| private long lastUpdateTime = 0; // Last time maps were updated |
| |
| /* |
| * Constructor |
| * @param conf the configuration |
| * @param constructFullMapAtInit initialize the maps with full mapping when |
| * true, otherwise initialize the maps to empty. This parameter is |
| * intended for testing only, its default is false. |
| */ |
| @VisibleForTesting |
| public ShellBasedIdMapping(Configuration conf, |
| boolean constructFullMapAtInit) throws IOException { |
| this.constructFullMapAtInit = constructFullMapAtInit; |
| long updateTime = conf.getLong( |
| IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY, |
| IdMappingConstant.USERGROUPID_UPDATE_MILLIS_DEFAULT); |
| // Minimal interval is 1 minute |
| if (updateTime < IdMappingConstant.USERGROUPID_UPDATE_MILLIS_MIN) { |
| LOG.info("User configured user account update time is less" |
| + " than 1 minute. Use 1 minute instead."); |
| timeout = IdMappingConstant.USERGROUPID_UPDATE_MILLIS_MIN; |
| } else { |
| timeout = updateTime; |
| } |
| |
| String staticFilePath = |
| conf.get(IdMappingConstant.STATIC_ID_MAPPING_FILE_KEY, |
| IdMappingConstant.STATIC_ID_MAPPING_FILE_DEFAULT); |
| staticMappingFile = new File(staticFilePath); |
| updateStaticMapping(); |
| updateMaps(); |
| } |
| |
| /* |
| * Constructor |
| * initialize user and group maps to empty |
| * @param conf the configuration |
| */ |
| public ShellBasedIdMapping(Configuration conf) throws IOException { |
| this(conf, false); |
| } |
| |
| @VisibleForTesting |
| public long getTimeout() { |
| return timeout; |
| } |
| |
| @VisibleForTesting |
| public BiMap<Integer, String> getUidNameMap() { |
| return uidNameMap; |
| } |
| |
| @VisibleForTesting |
| public BiMap<Integer, String> getGidNameMap() { |
| return gidNameMap; |
| } |
| |
| @VisibleForTesting |
| synchronized public void clearNameMaps() { |
| uidNameMap.clear(); |
| gidNameMap.clear(); |
| lastUpdateTime = Time.monotonicNow(); |
| } |
| |
| synchronized private boolean isExpired() { |
| return Time.monotonicNow() - lastUpdateTime > timeout; |
| } |
| |
| // If can't update the maps, will keep using the old ones |
| private void checkAndUpdateMaps() { |
| if (isExpired()) { |
| LOG.info("Update cache now"); |
| try { |
| updateMaps(); |
| } catch (IOException e) { |
| LOG.error("Can't update the maps. Will use the old ones," |
| + " which can potentially cause problem.", e); |
| } |
| } |
| } |
| |
| private static final String DUPLICATE_NAME_ID_DEBUG_INFO = |
| "NFS gateway could have problem starting with duplicate name or id on the host system.\n" |
| + "This is because HDFS (non-kerberos cluster) uses name as the only way to identify a user or group.\n" |
| + "The host system with duplicated user/group name or id might work fine most of the time by itself.\n" |
| + "However when NFS gateway talks to HDFS, HDFS accepts only user and group name.\n" |
| + "Therefore, same name means the same user or same group. To find the duplicated names/ids, one can do:\n" |
| + "<getent passwd | cut -d: -f1,3> and <getent group | cut -d: -f1,3> on Linux systems,\n" |
| + "<dscl . -list /Users UniqueID> and <dscl . -list /Groups PrimaryGroupID> on MacOS."; |
| |
| private static void reportDuplicateEntry(final String header, |
| final Integer key, final String value, |
| final Integer ekey, final String evalue) { |
| LOG.warn("\n" + header + String.format( |
| "new entry (%d, %s), existing entry: (%d, %s).%n%s%n%s", |
| key, value, ekey, evalue, |
| "The new entry is to be ignored for the following reason.", |
| DUPLICATE_NAME_ID_DEBUG_INFO)); |
| } |
| |
| /** |
| * uid and gid are defined as uint32 in linux. Some systems create |
| * (intended or unintended) <nfsnobody, 4294967294> kind of <name,Id> |
| * mapping, where 4294967294 is 2**32-2 as unsigned int32. As an example, |
| * https://bugzilla.redhat.com/show_bug.cgi?id=511876. |
| * Because user or group id are treated as Integer (signed integer or int32) |
| * here, the number 4294967294 is out of range. The solution is to convert |
| * uint32 to int32, so to map the out-of-range ID to the negative side of |
| * Integer, e.g. 4294967294 maps to -2 and 4294967295 maps to -1. |
| */ |
| private static Integer parseId(final String idStr) { |
| long longVal = Long.parseLong(idStr); |
| return Integer.valueOf((int)longVal); |
| } |
| |
| /** |
| * Get the list of users or groups returned by the specified command, |
| * and save them in the corresponding map. |
| * @throws IOException |
| */ |
| @VisibleForTesting |
| public static boolean updateMapInternal(BiMap<Integer, String> map, |
| String mapName, String command, String regex, |
| Map<Integer, Integer> staticMapping) throws IOException { |
| boolean updated = false; |
| BufferedReader br = null; |
| try { |
| Process process = Runtime.getRuntime().exec( |
| new String[] { "bash", "-c", command }); |
| br = new BufferedReader( |
| new InputStreamReader(process.getInputStream(), |
| Charset.defaultCharset())); |
| String line = null; |
| while ((line = br.readLine()) != null) { |
| String[] nameId = line.split(regex); |
| if ((nameId == null) || (nameId.length != 2)) { |
| throw new IOException("Can't parse " + mapName + " list entry:" + line); |
| } |
| LOG.debug("add to " + mapName + "map:" + nameId[0] + " id:" + nameId[1]); |
| // HDFS can't differentiate duplicate names with simple authentication |
| final Integer key = staticMapping.get(parseId(nameId[1])); |
| final String value = nameId[0]; |
| if (map.containsKey(key)) { |
| final String prevValue = map.get(key); |
| if (value.equals(prevValue)) { |
| // silently ignore equivalent entries |
| continue; |
| } |
| reportDuplicateEntry( |
| "Got multiple names associated with the same id: ", |
| key, value, key, prevValue); |
| continue; |
| } |
| if (map.containsValue(value)) { |
| final Integer prevKey = map.inverse().get(value); |
| reportDuplicateEntry( |
| "Got multiple ids associated with the same name: ", |
| key, value, prevKey, value); |
| continue; |
| } |
| map.put(key, value); |
| updated = true; |
| } |
| LOG.debug("Updated " + mapName + " map size: " + map.size()); |
| |
| } catch (IOException e) { |
| LOG.error("Can't update " + mapName + " map"); |
| throw e; |
| } finally { |
| if (br != null) { |
| try { |
| br.close(); |
| } catch (IOException e1) { |
| LOG.error("Can't close BufferedReader of command result", e1); |
| } |
| } |
| } |
| return updated; |
| } |
| |
| private boolean checkSupportedPlatform() { |
| if (!OS.startsWith("Linux") && !OS.startsWith("Mac")) { |
| LOG.error("Platform is not supported:" + OS |
| + ". Can't update user map and group map and" |
| + " 'nobody' will be used for any user and group."); |
| return false; |
| } |
| return true; |
| } |
| |
| private static boolean isInteger(final String s) { |
| try { |
| Integer.parseInt(s); |
| } catch(NumberFormatException e) { |
| return false; |
| } |
| // only got here if we didn't return false |
| return true; |
| } |
| |
| private synchronized void updateStaticMapping() throws IOException { |
| final boolean init = (staticMapping == null); |
| // |
| // if the static mapping file |
| // - was modified after last update, load the map again; |
| // - did not exist but was added since last update, load the map; |
| // - existed before but deleted since last update, clear the map |
| // |
| if (staticMappingFile.exists()) { |
| // check modification time, reload the file if the last modification |
| // time changed since prior load. |
| long lmTime = staticMappingFile.lastModified(); |
| if (lmTime != lastModificationTimeStaticMap) { |
| LOG.info(init? "Using " : "Reloading " + "'" + staticMappingFile |
| + "' for static UID/GID mapping..."); |
| lastModificationTimeStaticMap = lmTime; |
| staticMapping = parseStaticMap(staticMappingFile); |
| } |
| } else { |
| if (init) { |
| staticMapping = new StaticMapping(new HashMap<Integer, Integer>(), |
| new HashMap<Integer, Integer>()); |
| } |
| if (lastModificationTimeStaticMap != 0 || init) { |
| // print the following log at initialization or when the static |
| // mapping file was deleted after prior load |
| LOG.info("Not doing static UID/GID mapping because '" |
| + staticMappingFile + "' does not exist."); |
| } |
| lastModificationTimeStaticMap = 0; |
| staticMapping.clear(); |
| } |
| } |
| |
| /* |
| * Refresh static map, and reset the other maps to empty. |
| * For testing code, a full map may be re-constructed here when the object |
| * was created with constructFullMapAtInit being set to true. |
| */ |
| synchronized public void updateMaps() throws IOException { |
| if (!checkSupportedPlatform()) { |
| return; |
| } |
| |
| if (constructFullMapAtInit) { |
| loadFullMaps(); |
| // set constructFullMapAtInit to false to allow testing code to |
| // do incremental update to maps after initial construction |
| constructFullMapAtInit = false; |
| } else { |
| updateStaticMapping(); |
| clearNameMaps(); |
| } |
| } |
| |
| synchronized private void loadFullUserMap() throws IOException { |
| BiMap<Integer, String> uMap = HashBiMap.create(); |
| if (OS.startsWith("Mac")) { |
| updateMapInternal(uMap, "user", MAC_GET_ALL_USERS_CMD, "\\s+", |
| staticMapping.uidMapping); |
| } else { |
| updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":", |
| staticMapping.uidMapping); |
| } |
| uidNameMap = uMap; |
| lastUpdateTime = Time.monotonicNow(); |
| } |
| |
| synchronized private void loadFullGroupMap() throws IOException { |
| BiMap<Integer, String> gMap = HashBiMap.create(); |
| |
| if (OS.startsWith("Mac")) { |
| updateMapInternal(gMap, "group", MAC_GET_ALL_GROUPS_CMD, "\\s+", |
| staticMapping.gidMapping); |
| } else { |
| updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":", |
| staticMapping.gidMapping); |
| } |
| gidNameMap = gMap; |
| lastUpdateTime = Time.monotonicNow(); |
| } |
| |
| synchronized private void loadFullMaps() throws IOException { |
| loadFullUserMap(); |
| loadFullGroupMap(); |
| } |
| |
| // search for id with given name, return "<name>:<id>" |
| // return |
| // getent group <name> | cut -d: -f1,3 |
| // OR |
| // id -u <name> | awk '{print "<name>:"$1 }' |
| // |
| private String getName2IdCmdLinux(final String name, final boolean isGrp) { |
| String cmd; |
| if (isGrp) { |
| cmd = "getent group " + name + " | cut -d: -f1,3"; |
| } else { |
| cmd = "id -u " + name + " | awk '{print \"" + name + ":\"$1 }'"; |
| } |
| return cmd; |
| } |
| |
| // search for name with given id, return "<name>:<id>" |
| private String getId2NameCmdLinux(final int id, final boolean isGrp) { |
| String cmd = "getent "; |
| cmd += isGrp? "group " : "passwd "; |
| cmd += String.valueOf(id) + " | cut -d: -f1,3"; |
| return cmd; |
| } |
| |
| // "dscl . -read /Users/<name> | grep UniqueID" returns "UniqueId: <id>", |
| // "dscl . -read /Groups/<name> | grep PrimaryGroupID" returns "PrimaryGoupID: <id>" |
| // The following method returns a command that uses awk to process the result, |
| // of these commands, and returns "<name> <id>", to simulate one entry returned by |
| // MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD. |
| // Specificially, this method returns: |
| // id -u <name> | awk '{print "<name>:"$1 }' |
| // OR |
| // dscl . -read /Groups/<name> | grep PrimaryGroupID | awk '($1 == "PrimaryGroupID:") { print "<name> " $2 }' |
| // |
| private String getName2IdCmdMac(final String name, final boolean isGrp) { |
| String cmd; |
| if (isGrp) { |
| cmd = "dscl . -read /Groups/" + name; |
| cmd += " | grep PrimaryGroupID | awk '($1 == \"PrimaryGroupID:\") "; |
| cmd += "{ print \"" + name + " \" $2 }'"; |
| } else { |
| cmd = "id -u " + name + " | awk '{print \"" + name + " \"$1 }'"; |
| } |
| return cmd; |
| } |
| |
| // "dscl . -search /Users UniqueID <id>" returns |
| // <name> UniqueID = ( |
| // <id> |
| // ) |
| // "dscl . -search /Groups PrimaryGroupID <id>" returns |
| // <name> PrimaryGroupID = ( |
| // <id> |
| // ) |
| // The following method returns a command that uses sed to process the |
| // the result and returns "<name> <id>" to simulate one entry returned |
| // by MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD. |
| // For certain negative id case like nfsnobody, the <id> is quoted as |
| // "<id>", added one sed section to remove the quote. |
| // Specifically, the method returns: |
| // dscl . -search /Users UniqueID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/UniqueID =//g' | sed 's/)//g' | sed 's/\"//g' |
| // OR |
| // dscl . -search /Groups PrimaryGroupID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/PrimaryGroupID =//g' | sed 's/)//g' | sed 's/\"//g' |
| // |
| private String getId2NameCmdMac(final int id, final boolean isGrp) { |
| String cmd = "dscl . -search /"; |
| cmd += isGrp? "Groups PrimaryGroupID " : "Users UniqueID "; |
| cmd += String.valueOf(id); |
| cmd += " | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/"; |
| cmd += isGrp? "PrimaryGroupID" : "UniqueID"; |
| cmd += " = (//g' | sed 's/)//g' | sed 's/\\\"//g'"; |
| return cmd; |
| } |
| |
| synchronized private void updateMapIncr(final String name, |
| final boolean isGrp) throws IOException { |
| if (!checkSupportedPlatform()) { |
| return; |
| } |
| if (isInteger(name) && isGrp) { |
| loadFullGroupMap(); |
| return; |
| } |
| |
| boolean updated = false; |
| updateStaticMapping(); |
| |
| if (OS.startsWith("Linux")) { |
| if (isGrp) { |
| updated = updateMapInternal(gidNameMap, "group", |
| getName2IdCmdLinux(name, true), ":", |
| staticMapping.gidMapping); |
| } else { |
| updated = updateMapInternal(uidNameMap, "user", |
| getName2IdCmdLinux(name, false), ":", |
| staticMapping.uidMapping); |
| } |
| } else { |
| // Mac |
| if (isGrp) { |
| updated = updateMapInternal(gidNameMap, "group", |
| getName2IdCmdMac(name, true), "\\s+", |
| staticMapping.gidMapping); |
| } else { |
| updated = updateMapInternal(uidNameMap, "user", |
| getName2IdCmdMac(name, false), "\\s+", |
| staticMapping.uidMapping); |
| } |
| } |
| if (updated) { |
| lastUpdateTime = Time.monotonicNow(); |
| } |
| } |
| |
| synchronized private void updateMapIncr(final int id, |
| final boolean isGrp) throws IOException { |
| if (!checkSupportedPlatform()) { |
| return; |
| } |
| |
| boolean updated = false; |
| updateStaticMapping(); |
| |
| if (OS.startsWith("Linux")) { |
| if (isGrp) { |
| updated = updateMapInternal(gidNameMap, "group", |
| getId2NameCmdLinux(id, true), ":", |
| staticMapping.gidMapping); |
| } else { |
| updated = updateMapInternal(uidNameMap, "user", |
| getId2NameCmdLinux(id, false), ":", |
| staticMapping.uidMapping); |
| } |
| } else { |
| // Mac |
| if (isGrp) { |
| updated = updateMapInternal(gidNameMap, "group", |
| getId2NameCmdMac(id, true), "\\s+", |
| staticMapping.gidMapping); |
| } else { |
| updated = updateMapInternal(uidNameMap, "user", |
| getId2NameCmdMac(id, false), "\\s+", |
| staticMapping.uidMapping); |
| } |
| } |
| if (updated) { |
| lastUpdateTime = Time.monotonicNow(); |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| static final class PassThroughMap<K> extends HashMap<K, K> { |
| |
| public PassThroughMap() { |
| this(new HashMap<K, K>()); |
| } |
| |
| public PassThroughMap(Map<K, K> mapping) { |
| super(); |
| for (Map.Entry<K, K> entry : mapping.entrySet()) { |
| super.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public K get(Object key) { |
| if (super.containsKey(key)) { |
| return super.get(key); |
| } else { |
| return (K) key; |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| static final class StaticMapping { |
| final Map<Integer, Integer> uidMapping; |
| final Map<Integer, Integer> gidMapping; |
| |
| public StaticMapping(Map<Integer, Integer> uidMapping, |
| Map<Integer, Integer> gidMapping) { |
| this.uidMapping = new PassThroughMap<Integer>(uidMapping); |
| this.gidMapping = new PassThroughMap<Integer>(gidMapping); |
| } |
| |
| public void clear() { |
| uidMapping.clear(); |
| gidMapping.clear(); |
| } |
| |
| public boolean isNonEmpty() { |
| return uidMapping.size() > 0 || gidMapping.size() > 0; |
| } |
| } |
| |
| static StaticMapping parseStaticMap(File staticMapFile) |
| throws IOException { |
| |
| Map<Integer, Integer> uidMapping = new HashMap<Integer, Integer>(); |
| Map<Integer, Integer> gidMapping = new HashMap<Integer, Integer>(); |
| |
| BufferedReader in = new BufferedReader(new InputStreamReader( |
| new FileInputStream(staticMapFile), StandardCharsets.UTF_8)); |
| |
| try { |
| String line = null; |
| while ((line = in.readLine()) != null) { |
| // Skip entirely empty and comment lines. |
| if (EMPTY_LINE.matcher(line).matches() || |
| COMMENT_LINE.matcher(line).matches()) { |
| continue; |
| } |
| |
| Matcher lineMatcher = MAPPING_LINE.matcher(line); |
| if (!lineMatcher.matches()) { |
| LOG.warn("Could not parse line '" + line + "'. Lines should be of " + |
| "the form '[uid|gid] [remote id] [local id]'. Blank lines and " + |
| "everything following a '#' on a line will be ignored."); |
| continue; |
| } |
| |
| // We know the line is fine to parse without error checking like this |
| // since it matched the regex above. |
| String firstComponent = lineMatcher.group(1); |
| Integer remoteId = parseId(lineMatcher.group(2)); |
| Integer localId = parseId(lineMatcher.group(3)); |
| if (firstComponent.equals("uid")) { |
| uidMapping.put(localId, remoteId); |
| } else { |
| gidMapping.put(localId, remoteId); |
| } |
| } |
| } finally { |
| in.close(); |
| } |
| |
| return new StaticMapping(uidMapping, gidMapping); |
| } |
| |
| synchronized public int getUid(String user) throws IOException { |
| checkAndUpdateMaps(); |
| |
| Integer id = uidNameMap.inverse().get(user); |
| if (id == null) { |
| updateMapIncr(user, false); |
| id = uidNameMap.inverse().get(user); |
| if (id == null) { |
| throw new IOException("User just deleted?:" + user); |
| } |
| } |
| return id.intValue(); |
| } |
| |
| synchronized public int getGid(String group) throws IOException { |
| checkAndUpdateMaps(); |
| |
| Integer id = gidNameMap.inverse().get(group); |
| if (id == null) { |
| updateMapIncr(group, true); |
| id = gidNameMap.inverse().get(group); |
| if (id == null) { |
| throw new IOException("No such group:" + group); |
| } |
| } |
| return id.intValue(); |
| } |
| |
| synchronized public String getUserName(int uid, String unknown) { |
| checkAndUpdateMaps(); |
| String uname = uidNameMap.get(uid); |
| if (uname == null) { |
| try { |
| updateMapIncr(uid, false); |
| } catch (Exception e) { |
| } |
| uname = uidNameMap.get(uid); |
| if (uname == null) { |
| LOG.warn("Can't find user name for uid " + uid |
| + ". Use default user name " + unknown); |
| uname = unknown; |
| } |
| } |
| return uname; |
| } |
| |
| synchronized public String getGroupName(int gid, String unknown) { |
| checkAndUpdateMaps(); |
| String gname = gidNameMap.get(gid); |
| if (gname == null) { |
| try { |
| updateMapIncr(gid, true); |
| } catch (Exception e) { |
| } |
| gname = gidNameMap.get(gid); |
| if (gname == null) { |
| LOG.warn("Can't find group name for gid " + gid |
| + ". Use default group name " + unknown); |
| gname = unknown; |
| } |
| } |
| return gname; |
| } |
| |
| // When can't map user, return user name's string hashcode |
| public int getUidAllowingUnknown(String user) { |
| checkAndUpdateMaps(); |
| int uid; |
| try { |
| uid = getUid(user); |
| } catch (IOException e) { |
| uid = user.hashCode(); |
| LOG.info("Can't map user " + user + ". Use its string hashcode:" + uid); |
| } |
| return uid; |
| } |
| |
| // When can't map group, return group name's string hashcode |
| public int getGidAllowingUnknown(String group) { |
| checkAndUpdateMaps(); |
| int gid; |
| try { |
| gid = getGid(group); |
| } catch (IOException e) { |
| gid = group.hashCode(); |
| LOG.info("Can't map group " + group + ". Use its string hashcode:" + gid); |
| } |
| return gid; |
| } |
| } |