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}