001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.security;
019
020import java.io.BufferedReader;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.io.InputStreamReader;
025import java.nio.charset.Charset;
026import java.nio.charset.StandardCharsets;
027import java.util.HashMap;
028import java.util.Map;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.util.Time;
036
037import com.google.common.annotations.VisibleForTesting;
038import com.google.common.collect.BiMap;
039import com.google.common.collect.HashBiMap;
040
041/**
042 * A simple shell-based implementation of {@link IdMappingServiceProvider} 
043 * Map id to user name or group name. It does update every 15 minutes. Only a
044 * single instance of this class is expected to be on the server.
045 * 
046 * The maps are incrementally updated as described below:
047 *   1. Initialize the maps as empty. 
048 *   2. Incrementally update the maps
049 *      - When ShellBasedIdMapping is requested for user or group name given 
050 *        an ID, or for ID given a user or group name, do look up in the map
051 *        first, if it doesn't exist, find the corresponding entry with shell
052 *        command, and insert the entry to the maps.
053 *      - When group ID is requested for a given group name, and if the
054 *        group name is numerical, the full group map is loaded. Because we
055 *        don't have a good way to find the entry for a numerical group name,
056 *        loading the full map helps to get in all entries.
057 *   3. Periodically refresh the maps for both user and group, e.g,
058 *      do step 1.
059 *   Note: for testing purpose, step 1 may initial the maps with full mapping
060 *   when using constructor
061 *   {@link ShellBasedIdMapping#ShellBasedIdMapping(Configuration, boolean)}.
062 */
063public class ShellBasedIdMapping implements IdMappingServiceProvider {
064
065  private static final Log LOG =
066      LogFactory.getLog(ShellBasedIdMapping.class);
067
068  private final static String OS = System.getProperty("os.name");
069
070  /** Shell commands to get users and groups */
071  static final String GET_ALL_USERS_CMD = "getent passwd | cut -d: -f1,3";
072  static final String GET_ALL_GROUPS_CMD = "getent group | cut -d: -f1,3";
073  static final String MAC_GET_ALL_USERS_CMD = "dscl . -list /Users UniqueID";
074  static final String MAC_GET_ALL_GROUPS_CMD = "dscl . -list /Groups PrimaryGroupID";
075
076  private final File staticMappingFile;
077  private StaticMapping staticMapping = null;
078  // Last time the static map was modified, measured time difference in
079  // milliseconds since midnight, January 1, 1970 UTC
080  private long lastModificationTimeStaticMap = 0;
081  
082  private boolean constructFullMapAtInit = false;
083
084  // Used for parsing the static mapping file.
085  private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$");
086  private static final Pattern COMMENT_LINE = Pattern.compile("^\\s*#.*$");
087  private static final Pattern MAPPING_LINE =
088      Pattern.compile("^(uid|gid)\\s+(\\d+)\\s+(\\d+)\\s*(#.*)?$");
089
090  final private long timeout;
091  
092  // Maps for id to name map. Guarded by this object monitor lock
093  private BiMap<Integer, String> uidNameMap = HashBiMap.create();
094  private BiMap<Integer, String> gidNameMap = HashBiMap.create();
095  private long lastUpdateTime = 0; // Last time maps were updated
096
097  /*
098   * Constructor
099   * @param conf the configuration
100   * @param constructFullMapAtInit initialize the maps with full mapping when
101   *        true, otherwise initialize the maps to empty. This parameter is
102   *        intended for testing only, its default is false.
103   */
104  @VisibleForTesting
105  public ShellBasedIdMapping(Configuration conf,
106      boolean constructFullMapAtInit) throws IOException {
107    this.constructFullMapAtInit = constructFullMapAtInit;
108    long updateTime = conf.getLong(
109        IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY,
110        IdMappingConstant.USERGROUPID_UPDATE_MILLIS_DEFAULT);
111    // Minimal interval is 1 minute
112    if (updateTime < IdMappingConstant.USERGROUPID_UPDATE_MILLIS_MIN) {
113      LOG.info("User configured user account update time is less"
114          + " than 1 minute. Use 1 minute instead.");
115      timeout = IdMappingConstant.USERGROUPID_UPDATE_MILLIS_MIN;
116    } else {
117      timeout = updateTime;
118    }
119    
120    String staticFilePath = 
121        conf.get(IdMappingConstant.STATIC_ID_MAPPING_FILE_KEY,
122            IdMappingConstant.STATIC_ID_MAPPING_FILE_DEFAULT);
123    staticMappingFile = new File(staticFilePath);
124    updateStaticMapping();
125    updateMaps();
126  }
127
128  /*
129   * Constructor
130   * initialize user and group maps to empty
131   * @param conf the configuration
132   */
133  public ShellBasedIdMapping(Configuration conf) throws IOException {
134    this(conf, false);
135  }
136
137  @VisibleForTesting
138  public long getTimeout() {
139    return timeout;
140  }
141
142  @VisibleForTesting
143  public BiMap<Integer, String> getUidNameMap() {
144    return uidNameMap;
145  }
146
147  @VisibleForTesting
148  public BiMap<Integer, String> getGidNameMap() {
149    return gidNameMap;
150  }
151
152  @VisibleForTesting  
153  synchronized public void clearNameMaps() {
154    uidNameMap.clear();
155    gidNameMap.clear();
156    lastUpdateTime = Time.monotonicNow();
157  }  
158
159  synchronized private boolean isExpired() {
160    return Time.monotonicNow() - lastUpdateTime > timeout;
161  }
162
163  // If can't update the maps, will keep using the old ones
164  private void checkAndUpdateMaps() {
165    if (isExpired()) {
166      LOG.info("Update cache now");
167      try {
168        updateMaps();
169      } catch (IOException e) {
170        LOG.error("Can't update the maps. Will use the old ones,"
171            + " which can potentially cause problem.", e);
172      }
173    }
174  }
175
176  private static final String DUPLICATE_NAME_ID_DEBUG_INFO =
177      "NFS gateway could have problem starting with duplicate name or id on the host system.\n"
178      + "This is because HDFS (non-kerberos cluster) uses name as the only way to identify a user or group.\n"
179      + "The host system with duplicated user/group name or id might work fine most of the time by itself.\n"
180      + "However when NFS gateway talks to HDFS, HDFS accepts only user and group name.\n"
181      + "Therefore, same name means the same user or same group. To find the duplicated names/ids, one can do:\n"
182      + "<getent passwd | cut -d: -f1,3> and <getent group | cut -d: -f1,3> on Linux systems,\n"
183      + "<dscl . -list /Users UniqueID> and <dscl . -list /Groups PrimaryGroupID> on MacOS.";
184  
185  private static void reportDuplicateEntry(final String header,
186      final Integer key, final String value,
187      final Integer ekey, final String evalue) {    
188      LOG.warn("\n" + header + String.format(
189          "new entry (%d, %s), existing entry: (%d, %s).%n%s%n%s",
190          key, value, ekey, evalue,
191          "The new entry is to be ignored for the following reason.",
192          DUPLICATE_NAME_ID_DEBUG_INFO));
193  }
194
195  /**
196   * uid and gid are defined as uint32 in linux. Some systems create
197   * (intended or unintended) <nfsnobody, 4294967294> kind of <name,Id>
198   * mapping, where 4294967294 is 2**32-2 as unsigned int32. As an example,
199   *   https://bugzilla.redhat.com/show_bug.cgi?id=511876.
200   * Because user or group id are treated as Integer (signed integer or int32)
201   * here, the number 4294967294 is out of range. The solution is to convert
202   * uint32 to int32, so to map the out-of-range ID to the negative side of
203   * Integer, e.g. 4294967294 maps to -2 and 4294967295 maps to -1.
204   */
205  private static Integer parseId(final String idStr) {
206    long longVal = Long.parseLong(idStr);
207    return Integer.valueOf((int)longVal);
208  }
209  
210  /**
211   * Get the list of users or groups returned by the specified command,
212   * and save them in the corresponding map.
213   * @throws IOException 
214   */
215  @VisibleForTesting
216  public static boolean updateMapInternal(BiMap<Integer, String> map,
217      String mapName, String command, String regex,
218      Map<Integer, Integer> staticMapping) throws IOException  {
219    boolean updated = false;
220    BufferedReader br = null;
221    try {
222      Process process = Runtime.getRuntime().exec(
223          new String[] { "bash", "-c", command });
224      br = new BufferedReader(
225          new InputStreamReader(process.getInputStream(),
226                                Charset.defaultCharset()));
227      String line = null;
228      while ((line = br.readLine()) != null) {
229        String[] nameId = line.split(regex);
230        if ((nameId == null) || (nameId.length != 2)) {
231          throw new IOException("Can't parse " + mapName + " list entry:" + line);
232        }
233        LOG.debug("add to " + mapName + "map:" + nameId[0] + " id:" + nameId[1]);
234        // HDFS can't differentiate duplicate names with simple authentication
235        final Integer key = staticMapping.get(parseId(nameId[1]));
236        final String value = nameId[0];
237        if (map.containsKey(key)) {
238          final String prevValue = map.get(key);
239          if (value.equals(prevValue)) {
240            // silently ignore equivalent entries
241            continue;
242          }
243          reportDuplicateEntry(
244              "Got multiple names associated with the same id: ",
245              key, value, key, prevValue);           
246          continue;
247        }
248        if (map.containsValue(value)) {
249          final Integer prevKey = map.inverse().get(value);
250          reportDuplicateEntry(
251              "Got multiple ids associated with the same name: ",
252              key, value, prevKey, value);
253          continue;
254        }
255        map.put(key, value);
256        updated = true;
257      }
258      LOG.debug("Updated " + mapName + " map size: " + map.size());
259      
260    } catch (IOException e) {
261      LOG.error("Can't update " + mapName + " map");
262      throw e;
263    } finally {
264      if (br != null) {
265        try {
266          br.close();
267        } catch (IOException e1) {
268          LOG.error("Can't close BufferedReader of command result", e1);
269        }
270      }
271    }
272    return updated;
273  }
274
275  private boolean checkSupportedPlatform() {
276    if (!OS.startsWith("Linux") && !OS.startsWith("Mac")) {
277      LOG.error("Platform is not supported:" + OS
278          + ". Can't update user map and group map and"
279          + " 'nobody' will be used for any user and group.");
280      return false;
281    }
282    return true;
283  }
284
285  private static boolean isInteger(final String s) {
286    try { 
287      Integer.parseInt(s); 
288    } catch(NumberFormatException e) { 
289      return false; 
290    }
291    // only got here if we didn't return false
292    return true;
293  }
294
295  private synchronized void updateStaticMapping() throws IOException {
296    final boolean init = (staticMapping == null);
297    //
298    // if the static mapping file
299    //   - was modified after last update, load the map again;
300    //   - did not exist but was added since last update, load the map;
301    //   - existed before but deleted since last update, clear the map
302    //
303    if (staticMappingFile.exists()) {
304      // check modification time, reload the file if the last modification
305      // time changed since prior load.
306      long lmTime = staticMappingFile.lastModified();
307      if (lmTime != lastModificationTimeStaticMap) {
308        LOG.info(init? "Using " : "Reloading " + "'" + staticMappingFile
309            + "' for static UID/GID mapping...");
310        lastModificationTimeStaticMap = lmTime;
311        staticMapping = parseStaticMap(staticMappingFile);        
312      }
313    } else {
314      if (init) {
315        staticMapping = new StaticMapping(new HashMap<Integer, Integer>(),
316            new HashMap<Integer, Integer>());
317      }
318      if (lastModificationTimeStaticMap != 0 || init) {
319        // print the following log at initialization or when the static
320        // mapping file was deleted after prior load
321        LOG.info("Not doing static UID/GID mapping because '"
322            + staticMappingFile + "' does not exist.");
323      }
324      lastModificationTimeStaticMap = 0;
325      staticMapping.clear();
326    }
327  }
328
329  /*
330   * Refresh static map, and reset the other maps to empty.
331   * For testing code, a full map may be re-constructed here when the object
332   * was created with constructFullMapAtInit being set to true.
333   */
334  synchronized public void updateMaps() throws IOException {
335    if (!checkSupportedPlatform()) {
336      return;
337    }
338
339    if (constructFullMapAtInit) {
340      loadFullMaps();
341      // set constructFullMapAtInit to false to allow testing code to
342      // do incremental update to maps after initial construction
343      constructFullMapAtInit = false;
344    } else {
345      updateStaticMapping();
346      clearNameMaps();
347    }
348  }
349  
350  synchronized private void loadFullUserMap() throws IOException {
351    BiMap<Integer, String> uMap = HashBiMap.create();
352    if (OS.startsWith("Mac")) {
353      updateMapInternal(uMap, "user", MAC_GET_ALL_USERS_CMD, "\\s+",
354          staticMapping.uidMapping);
355    } else {
356      updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":",
357          staticMapping.uidMapping);
358    }
359    uidNameMap = uMap;
360    lastUpdateTime = Time.monotonicNow();
361  }
362
363  synchronized private void loadFullGroupMap() throws IOException {
364    BiMap<Integer, String> gMap = HashBiMap.create();
365
366    if (OS.startsWith("Mac")) {
367      updateMapInternal(gMap, "group", MAC_GET_ALL_GROUPS_CMD, "\\s+",
368          staticMapping.gidMapping);
369    } else {
370      updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":",
371          staticMapping.gidMapping);
372    }
373    gidNameMap = gMap;
374    lastUpdateTime = Time.monotonicNow();
375  }
376
377  synchronized private void loadFullMaps() throws IOException {
378    loadFullUserMap();
379    loadFullGroupMap();
380  }
381
382  // search for id with given name, return "<name>:<id>"
383  // return
384  //     getent group <name> | cut -d: -f1,3
385  // OR
386  //     id -u <name> | awk '{print "<name>:"$1 }'
387  //
388  private String getName2IdCmdLinux(final String name, final boolean isGrp) {
389    String cmd;
390    if (isGrp) {
391      cmd = "getent group " + name + " | cut -d: -f1,3";   
392    } else {
393      cmd = "id -u " + name + " | awk '{print \"" + name + ":\"$1 }'";
394    }
395    return cmd;
396  }
397  
398  // search for name with given id, return "<name>:<id>"
399  private String getId2NameCmdLinux(final int id, final boolean isGrp) {
400    String cmd = "getent ";
401    cmd += isGrp? "group " : "passwd ";
402    cmd += String.valueOf(id) + " | cut -d: -f1,3";
403    return cmd;
404  }
405
406  // "dscl . -read /Users/<name> | grep UniqueID" returns "UniqueId: <id>",
407  // "dscl . -read /Groups/<name> | grep PrimaryGroupID" returns "PrimaryGoupID: <id>"
408  // The following method returns a command that uses awk to process the result,
409  // of these commands, and returns "<name> <id>", to simulate one entry returned by 
410  // MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD.
411  // Specificially, this method returns:
412  // id -u <name> | awk '{print "<name>:"$1 }'
413  // OR
414  // dscl . -read /Groups/<name> | grep PrimaryGroupID | awk '($1 == "PrimaryGroupID:") { print "<name> " $2 }'
415  //
416  private String getName2IdCmdMac(final String name, final boolean isGrp) {
417    String cmd;
418    if (isGrp) {
419      cmd = "dscl . -read /Groups/" + name;
420      cmd += " | grep PrimaryGroupID | awk '($1 == \"PrimaryGroupID:\") ";
421      cmd += "{ print \"" + name + "  \" $2 }'";
422    } else {
423      cmd = "id -u " + name + " | awk '{print \"" + name + "  \"$1 }'";
424    }
425    return cmd;
426  }
427
428  // "dscl . -search /Users UniqueID <id>" returns 
429  //    <name> UniqueID = (
430  //      <id>
431  //    )
432  // "dscl . -search /Groups PrimaryGroupID <id>" returns
433  //    <name> PrimaryGroupID = (
434  //      <id>
435  //    )
436  // The following method returns a command that uses sed to process the
437  // the result and returns "<name> <id>" to simulate one entry returned
438  // by MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD.
439  // For certain negative id case like nfsnobody, the <id> is quoted as
440  // "<id>", added one sed section to remove the quote.
441  // Specifically, the method returns:
442  // dscl . -search /Users UniqueID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/UniqueID =//g' | sed 's/)//g' | sed 's/\"//g'
443  // OR
444  // dscl . -search /Groups PrimaryGroupID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/PrimaryGroupID =//g' | sed 's/)//g' | sed 's/\"//g'
445  //
446  private String getId2NameCmdMac(final int id, final boolean isGrp) {
447    String cmd = "dscl . -search /";
448    cmd += isGrp? "Groups PrimaryGroupID " : "Users UniqueID ";
449    cmd += String.valueOf(id);
450    cmd += " | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/";
451    cmd += isGrp? "PrimaryGroupID" : "UniqueID";
452    cmd += " = (//g' | sed 's/)//g' | sed 's/\\\"//g'";
453    return cmd;
454  }
455
456  synchronized private void updateMapIncr(final String name,
457      final boolean isGrp) throws IOException {
458    if (!checkSupportedPlatform()) {
459      return;
460    }
461    if (isInteger(name) && isGrp) {
462      loadFullGroupMap();
463      return;
464    }
465
466    boolean updated = false;
467    updateStaticMapping();
468
469    if (OS.startsWith("Linux")) {
470      if (isGrp) {
471        updated = updateMapInternal(gidNameMap, "group",
472            getName2IdCmdLinux(name, true), ":",
473            staticMapping.gidMapping);
474      } else {
475        updated = updateMapInternal(uidNameMap, "user",
476            getName2IdCmdLinux(name, false), ":",
477            staticMapping.uidMapping);
478      }
479    } else {
480      // Mac
481      if (isGrp) {        
482        updated = updateMapInternal(gidNameMap, "group",
483            getName2IdCmdMac(name, true), "\\s+",
484            staticMapping.gidMapping);
485      } else {
486        updated = updateMapInternal(uidNameMap, "user",
487            getName2IdCmdMac(name, false), "\\s+",
488            staticMapping.uidMapping);
489      }
490    }
491    if (updated) {
492      lastUpdateTime = Time.monotonicNow();
493    }
494  }
495
496  synchronized private void updateMapIncr(final int id,
497      final boolean isGrp) throws IOException {
498    if (!checkSupportedPlatform()) {
499      return;
500    }
501    
502    boolean updated = false;
503    updateStaticMapping();
504
505    if (OS.startsWith("Linux")) {
506      if (isGrp) {
507        updated = updateMapInternal(gidNameMap, "group",
508            getId2NameCmdLinux(id, true), ":",
509            staticMapping.gidMapping);
510      } else {
511        updated = updateMapInternal(uidNameMap, "user",
512            getId2NameCmdLinux(id, false), ":",
513            staticMapping.uidMapping);
514      }
515    } else {
516      // Mac
517      if (isGrp) {
518        updated = updateMapInternal(gidNameMap, "group",
519            getId2NameCmdMac(id, true), "\\s+",
520            staticMapping.gidMapping);
521      } else {
522        updated = updateMapInternal(uidNameMap, "user",
523            getId2NameCmdMac(id, false), "\\s+",
524            staticMapping.uidMapping);
525      }
526    }
527    if (updated) {
528      lastUpdateTime = Time.monotonicNow();
529    }
530  }
531
532  @SuppressWarnings("serial")
533  static final class PassThroughMap<K> extends HashMap<K, K> {
534    
535    public PassThroughMap() {
536      this(new HashMap<K, K>());
537    }
538    
539    public PassThroughMap(Map<K, K> mapping) {
540      super();
541      for (Map.Entry<K, K> entry : mapping.entrySet()) {
542        super.put(entry.getKey(), entry.getValue());
543      }
544    }
545
546    @SuppressWarnings("unchecked")
547    @Override
548    public K get(Object key) {
549      if (super.containsKey(key)) {
550        return super.get(key);
551      } else {
552        return (K) key;
553      }
554    }
555  }
556  
557  @VisibleForTesting
558  static final class StaticMapping {
559    final Map<Integer, Integer> uidMapping;
560    final Map<Integer, Integer> gidMapping;
561    
562    public StaticMapping(Map<Integer, Integer> uidMapping,
563        Map<Integer, Integer> gidMapping) {
564      this.uidMapping = new PassThroughMap<Integer>(uidMapping);
565      this.gidMapping = new PassThroughMap<Integer>(gidMapping);
566    }
567
568    public void clear() {
569      uidMapping.clear();
570      gidMapping.clear();
571    }
572
573    public boolean isNonEmpty() {
574      return uidMapping.size() > 0 || gidMapping.size() > 0;
575    }
576  }
577  
578  static StaticMapping parseStaticMap(File staticMapFile)
579      throws IOException {
580    
581    Map<Integer, Integer> uidMapping = new HashMap<Integer, Integer>();
582    Map<Integer, Integer> gidMapping = new HashMap<Integer, Integer>();
583    
584    BufferedReader in = new BufferedReader(new InputStreamReader(
585        new FileInputStream(staticMapFile), StandardCharsets.UTF_8));
586    
587    try {
588      String line = null;
589      while ((line = in.readLine()) != null) {
590        // Skip entirely empty and comment lines.
591        if (EMPTY_LINE.matcher(line).matches() ||
592            COMMENT_LINE.matcher(line).matches()) {
593          continue;
594        }
595        
596        Matcher lineMatcher = MAPPING_LINE.matcher(line);
597        if (!lineMatcher.matches()) {
598          LOG.warn("Could not parse line '" + line + "'. Lines should be of " +
599              "the form '[uid|gid] [remote id] [local id]'. Blank lines and " +
600              "everything following a '#' on a line will be ignored.");
601          continue;
602        }
603        
604        // We know the line is fine to parse without error checking like this
605        // since it matched the regex above.
606        String firstComponent = lineMatcher.group(1);
607        Integer remoteId = parseId(lineMatcher.group(2));
608        Integer localId = parseId(lineMatcher.group(3));
609        if (firstComponent.equals("uid")) {
610          uidMapping.put(localId, remoteId);
611        } else {
612          gidMapping.put(localId, remoteId);
613        }
614      }
615    } finally {
616      in.close();
617    }
618    
619    return new StaticMapping(uidMapping, gidMapping);
620  }
621
622  synchronized public int getUid(String user) throws IOException {
623    checkAndUpdateMaps();
624
625    Integer id = uidNameMap.inverse().get(user);
626    if (id == null) {
627      updateMapIncr(user, false);
628      id = uidNameMap.inverse().get(user);
629      if (id == null) {
630        throw new IOException("User just deleted?:" + user);
631      }
632    }
633    return id.intValue();
634  }
635
636  synchronized public int getGid(String group) throws IOException {
637    checkAndUpdateMaps();
638
639    Integer id = gidNameMap.inverse().get(group);
640    if (id == null) {
641      updateMapIncr(group, true);
642      id = gidNameMap.inverse().get(group);
643      if (id == null) {
644        throw new IOException("No such group:" + group);
645      }
646    }
647    return id.intValue();
648  }
649
650  synchronized public String getUserName(int uid, String unknown) {
651    checkAndUpdateMaps();
652    String uname = uidNameMap.get(uid);
653    if (uname == null) {
654      try {
655        updateMapIncr(uid, false);
656      } catch (Exception e) {        
657      }
658      uname = uidNameMap.get(uid);
659      if (uname == null) {     
660        LOG.warn("Can't find user name for uid " + uid
661            + ". Use default user name " + unknown);
662        uname = unknown;
663      }
664    }
665    return uname;
666  }
667
668  synchronized public String getGroupName(int gid, String unknown) {
669    checkAndUpdateMaps();
670    String gname = gidNameMap.get(gid);
671    if (gname == null) {
672      try {
673        updateMapIncr(gid, true);
674      } catch (Exception e) {        
675      }
676      gname = gidNameMap.get(gid);
677      if (gname == null) {
678        LOG.warn("Can't find group name for gid " + gid
679            + ". Use default group name " + unknown);
680        gname = unknown;
681      }
682    }
683    return gname;
684  }
685
686  // When can't map user, return user name's string hashcode
687  public int getUidAllowingUnknown(String user) {
688    checkAndUpdateMaps();
689    int uid;
690    try {
691      uid = getUid(user);
692    } catch (IOException e) {
693      uid = user.hashCode();
694      LOG.info("Can't map user " + user + ". Use its string hashcode:" + uid);
695    }
696    return uid;
697  }
698
699  // When can't map group, return group name's string hashcode
700  public int getGidAllowingUnknown(String group) {
701    checkAndUpdateMaps();
702    int gid;
703    try {
704      gid = getGid(group);
705    } catch (IOException e) {
706      gid = group.hashCode();
707      LOG.info("Can't map group " + group + ". Use its string hashcode:" + gid);
708    }
709    return gid;
710  }
711}