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.util.HashMap; 027import java.util.Map; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.apache.commons.io.Charsets; 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 int intVal = longVal.intValue(); 208 return Integer.valueOf(intVal); 209 } 210 211 /** 212 * Get the list of users or groups returned by the specified command, 213 * and save them in the corresponding map. 214 * @throws IOException 215 */ 216 @VisibleForTesting 217 public static boolean updateMapInternal(BiMap<Integer, String> map, 218 String mapName, String command, String regex, 219 Map<Integer, Integer> staticMapping) throws IOException { 220 boolean updated = false; 221 BufferedReader br = null; 222 try { 223 Process process = Runtime.getRuntime().exec( 224 new String[] { "bash", "-c", command }); 225 br = new BufferedReader( 226 new InputStreamReader(process.getInputStream(), 227 Charset.defaultCharset())); 228 String line = null; 229 while ((line = br.readLine()) != null) { 230 String[] nameId = line.split(regex); 231 if ((nameId == null) || (nameId.length != 2)) { 232 throw new IOException("Can't parse " + mapName + " list entry:" + line); 233 } 234 LOG.debug("add to " + mapName + "map:" + nameId[0] + " id:" + nameId[1]); 235 // HDFS can't differentiate duplicate names with simple authentication 236 final Integer key = staticMapping.get(parseId(nameId[1])); 237 final String value = nameId[0]; 238 if (map.containsKey(key)) { 239 final String prevValue = map.get(key); 240 if (value.equals(prevValue)) { 241 // silently ignore equivalent entries 242 continue; 243 } 244 reportDuplicateEntry( 245 "Got multiple names associated with the same id: ", 246 key, value, key, prevValue); 247 continue; 248 } 249 if (map.containsValue(value)) { 250 final Integer prevKey = map.inverse().get(value); 251 reportDuplicateEntry( 252 "Got multiple ids associated with the same name: ", 253 key, value, prevKey, value); 254 continue; 255 } 256 map.put(key, value); 257 updated = true; 258 } 259 LOG.debug("Updated " + mapName + " map size: " + map.size()); 260 261 } catch (IOException e) { 262 LOG.error("Can't update " + mapName + " map"); 263 throw e; 264 } finally { 265 if (br != null) { 266 try { 267 br.close(); 268 } catch (IOException e1) { 269 LOG.error("Can't close BufferedReader of command result", e1); 270 } 271 } 272 } 273 return updated; 274 } 275 276 private boolean checkSupportedPlatform() { 277 if (!OS.startsWith("Linux") && !OS.startsWith("Mac")) { 278 LOG.error("Platform is not supported:" + OS 279 + ". Can't update user map and group map and" 280 + " 'nobody' will be used for any user and group."); 281 return false; 282 } 283 return true; 284 } 285 286 private static boolean isInteger(final String s) { 287 try { 288 Integer.parseInt(s); 289 } catch(NumberFormatException e) { 290 return false; 291 } 292 // only got here if we didn't return false 293 return true; 294 } 295 296 private synchronized void updateStaticMapping() throws IOException { 297 final boolean init = (staticMapping == null); 298 // 299 // if the static mapping file 300 // - was modified after last update, load the map again; 301 // - did not exist but was added since last update, load the map; 302 // - existed before but deleted since last update, clear the map 303 // 304 if (staticMappingFile.exists()) { 305 // check modification time, reload the file if the last modification 306 // time changed since prior load. 307 long lmTime = staticMappingFile.lastModified(); 308 if (lmTime != lastModificationTimeStaticMap) { 309 LOG.info(init? "Using " : "Reloading " + "'" + staticMappingFile 310 + "' for static UID/GID mapping..."); 311 lastModificationTimeStaticMap = lmTime; 312 staticMapping = parseStaticMap(staticMappingFile); 313 } 314 } else { 315 if (init) { 316 staticMapping = new StaticMapping(new HashMap<Integer, Integer>(), 317 new HashMap<Integer, Integer>()); 318 } 319 if (lastModificationTimeStaticMap != 0 || init) { 320 // print the following log at initialization or when the static 321 // mapping file was deleted after prior load 322 LOG.info("Not doing static UID/GID mapping because '" 323 + staticMappingFile + "' does not exist."); 324 } 325 lastModificationTimeStaticMap = 0; 326 staticMapping.clear(); 327 } 328 } 329 330 /* 331 * Refresh static map, and reset the other maps to empty. 332 * For testing code, a full map may be re-constructed here when the object 333 * was created with constructFullMapAtInit being set to true. 334 */ 335 synchronized public void updateMaps() throws IOException { 336 if (!checkSupportedPlatform()) { 337 return; 338 } 339 340 if (constructFullMapAtInit) { 341 loadFullMaps(); 342 // set constructFullMapAtInit to false to allow testing code to 343 // do incremental update to maps after initial construction 344 constructFullMapAtInit = false; 345 } else { 346 updateStaticMapping(); 347 clearNameMaps(); 348 } 349 } 350 351 synchronized private void loadFullUserMap() throws IOException { 352 BiMap<Integer, String> uMap = HashBiMap.create(); 353 if (OS.startsWith("Mac")) { 354 updateMapInternal(uMap, "user", MAC_GET_ALL_USERS_CMD, "\\s+", 355 staticMapping.uidMapping); 356 } else { 357 updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":", 358 staticMapping.uidMapping); 359 } 360 uidNameMap = uMap; 361 lastUpdateTime = Time.monotonicNow(); 362 } 363 364 synchronized private void loadFullGroupMap() throws IOException { 365 BiMap<Integer, String> gMap = HashBiMap.create(); 366 367 if (OS.startsWith("Mac")) { 368 updateMapInternal(gMap, "group", MAC_GET_ALL_GROUPS_CMD, "\\s+", 369 staticMapping.gidMapping); 370 } else { 371 updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":", 372 staticMapping.gidMapping); 373 } 374 gidNameMap = gMap; 375 lastUpdateTime = Time.monotonicNow(); 376 } 377 378 synchronized private void loadFullMaps() throws IOException { 379 loadFullUserMap(); 380 loadFullGroupMap(); 381 } 382 383 // search for id with given name, return "<name>:<id>" 384 // return 385 // getent group <name> | cut -d: -f1,3 386 // OR 387 // id -u <name> | awk '{print "<name>:"$1 }' 388 // 389 private String getName2IdCmdLinux(final String name, final boolean isGrp) { 390 String cmd; 391 if (isGrp) { 392 cmd = "getent group " + name + " | cut -d: -f1,3"; 393 } else { 394 cmd = "id -u " + name + " | awk '{print \"" + name + ":\"$1 }'"; 395 } 396 return cmd; 397 } 398 399 // search for name with given id, return "<name>:<id>" 400 private String getId2NameCmdLinux(final int id, final boolean isGrp) { 401 String cmd = "getent "; 402 cmd += isGrp? "group " : "passwd "; 403 cmd += String.valueOf(id) + " | cut -d: -f1,3"; 404 return cmd; 405 } 406 407 // "dscl . -read /Users/<name> | grep UniqueID" returns "UniqueId: <id>", 408 // "dscl . -read /Groups/<name> | grep PrimaryGroupID" returns "PrimaryGoupID: <id>" 409 // The following method returns a command that uses awk to process the result, 410 // of these commands, and returns "<name> <id>", to simulate one entry returned by 411 // MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD. 412 // Specificially, this method returns: 413 // id -u <name> | awk '{print "<name>:"$1 }' 414 // OR 415 // dscl . -read /Groups/<name> | grep PrimaryGroupID | awk '($1 == "PrimaryGroupID:") { print "<name> " $2 }' 416 // 417 private String getName2IdCmdMac(final String name, final boolean isGrp) { 418 String cmd; 419 if (isGrp) { 420 cmd = "dscl . -read /Groups/" + name; 421 cmd += " | grep PrimaryGroupID | awk '($1 == \"PrimaryGroupID:\") "; 422 cmd += "{ print \"" + name + " \" $2 }'"; 423 } else { 424 cmd = "id -u " + name + " | awk '{print \"" + name + " \"$1 }'"; 425 } 426 return cmd; 427 } 428 429 // "dscl . -search /Users UniqueID <id>" returns 430 // <name> UniqueID = ( 431 // <id> 432 // ) 433 // "dscl . -search /Groups PrimaryGroupID <id>" returns 434 // <name> PrimaryGroupID = ( 435 // <id> 436 // ) 437 // The following method returns a command that uses sed to process the 438 // the result and returns "<name> <id>" to simulate one entry returned 439 // by MAC_GET_ALL_USERS_CMD or MAC_GET_ALL_GROUPS_CMD. 440 // For certain negative id case like nfsnobody, the <id> is quoted as 441 // "<id>", added one sed section to remove the quote. 442 // Specifically, the method returns: 443 // dscl . -search /Users UniqueID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/UniqueID =//g' | sed 's/)//g' | sed 's/\"//g' 444 // OR 445 // dscl . -search /Groups PrimaryGroupID <id> | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/PrimaryGroupID =//g' | sed 's/)//g' | sed 's/\"//g' 446 // 447 private String getId2NameCmdMac(final int id, final boolean isGrp) { 448 String cmd = "dscl . -search /"; 449 cmd += isGrp? "Groups PrimaryGroupID " : "Users UniqueID "; 450 cmd += String.valueOf(id); 451 cmd += " | sed 'N;s/\\n//g;N;s/\\n//g' | sed 's/"; 452 cmd += isGrp? "PrimaryGroupID" : "UniqueID"; 453 cmd += " = (//g' | sed 's/)//g' | sed 's/\\\"//g'"; 454 return cmd; 455 } 456 457 synchronized private void updateMapIncr(final String name, 458 final boolean isGrp) throws IOException { 459 if (!checkSupportedPlatform()) { 460 return; 461 } 462 if (isInteger(name) && isGrp) { 463 loadFullGroupMap(); 464 return; 465 } 466 467 boolean updated = false; 468 updateStaticMapping(); 469 470 if (OS.startsWith("Linux")) { 471 if (isGrp) { 472 updated = updateMapInternal(gidNameMap, "group", 473 getName2IdCmdLinux(name, true), ":", 474 staticMapping.gidMapping); 475 } else { 476 updated = updateMapInternal(uidNameMap, "user", 477 getName2IdCmdLinux(name, false), ":", 478 staticMapping.uidMapping); 479 } 480 } else { 481 // Mac 482 if (isGrp) { 483 updated = updateMapInternal(gidNameMap, "group", 484 getName2IdCmdMac(name, true), "\\s+", 485 staticMapping.gidMapping); 486 } else { 487 updated = updateMapInternal(uidNameMap, "user", 488 getName2IdCmdMac(name, false), "\\s+", 489 staticMapping.uidMapping); 490 } 491 } 492 if (updated) { 493 lastUpdateTime = Time.monotonicNow(); 494 } 495 } 496 497 synchronized private void updateMapIncr(final int id, 498 final boolean isGrp) throws IOException { 499 if (!checkSupportedPlatform()) { 500 return; 501 } 502 503 boolean updated = false; 504 updateStaticMapping(); 505 506 if (OS.startsWith("Linux")) { 507 if (isGrp) { 508 updated = updateMapInternal(gidNameMap, "group", 509 getId2NameCmdLinux(id, true), ":", 510 staticMapping.gidMapping); 511 } else { 512 updated = updateMapInternal(uidNameMap, "user", 513 getId2NameCmdLinux(id, false), ":", 514 staticMapping.uidMapping); 515 } 516 } else { 517 // Mac 518 if (isGrp) { 519 updated = updateMapInternal(gidNameMap, "group", 520 getId2NameCmdMac(id, true), "\\s+", 521 staticMapping.gidMapping); 522 } else { 523 updated = updateMapInternal(uidNameMap, "user", 524 getId2NameCmdMac(id, false), "\\s+", 525 staticMapping.uidMapping); 526 } 527 } 528 if (updated) { 529 lastUpdateTime = Time.monotonicNow(); 530 } 531 } 532 533 @SuppressWarnings("serial") 534 static final class PassThroughMap<K> extends HashMap<K, K> { 535 536 public PassThroughMap() { 537 this(new HashMap<K, K>()); 538 } 539 540 public PassThroughMap(Map<K, K> mapping) { 541 super(); 542 for (Map.Entry<K, K> entry : mapping.entrySet()) { 543 super.put(entry.getKey(), entry.getValue()); 544 } 545 } 546 547 @SuppressWarnings("unchecked") 548 @Override 549 public K get(Object key) { 550 if (super.containsKey(key)) { 551 return super.get(key); 552 } else { 553 return (K) key; 554 } 555 } 556 } 557 558 @VisibleForTesting 559 static final class StaticMapping { 560 final Map<Integer, Integer> uidMapping; 561 final Map<Integer, Integer> gidMapping; 562 563 public StaticMapping(Map<Integer, Integer> uidMapping, 564 Map<Integer, Integer> gidMapping) { 565 this.uidMapping = new PassThroughMap<Integer>(uidMapping); 566 this.gidMapping = new PassThroughMap<Integer>(gidMapping); 567 } 568 569 public void clear() { 570 uidMapping.clear(); 571 gidMapping.clear(); 572 } 573 574 public boolean isNonEmpty() { 575 return uidMapping.size() > 0 || gidMapping.size() > 0; 576 } 577 } 578 579 static StaticMapping parseStaticMap(File staticMapFile) 580 throws IOException { 581 582 Map<Integer, Integer> uidMapping = new HashMap<Integer, Integer>(); 583 Map<Integer, Integer> gidMapping = new HashMap<Integer, Integer>(); 584 585 BufferedReader in = new BufferedReader(new InputStreamReader( 586 new FileInputStream(staticMapFile), Charsets.UTF_8)); 587 588 try { 589 String line = null; 590 while ((line = in.readLine()) != null) { 591 // Skip entirely empty and comment lines. 592 if (EMPTY_LINE.matcher(line).matches() || 593 COMMENT_LINE.matcher(line).matches()) { 594 continue; 595 } 596 597 Matcher lineMatcher = MAPPING_LINE.matcher(line); 598 if (!lineMatcher.matches()) { 599 LOG.warn("Could not parse line '" + line + "'. Lines should be of " + 600 "the form '[uid|gid] [remote id] [local id]'. Blank lines and " + 601 "everything following a '#' on a line will be ignored."); 602 continue; 603 } 604 605 // We know the line is fine to parse without error checking like this 606 // since it matched the regex above. 607 String firstComponent = lineMatcher.group(1); 608 int remoteId = parseId(lineMatcher.group(2)); 609 int localId = parseId(lineMatcher.group(3)); 610 if (firstComponent.equals("uid")) { 611 uidMapping.put(localId, remoteId); 612 } else { 613 gidMapping.put(localId, remoteId); 614 } 615 } 616 } finally { 617 in.close(); 618 } 619 620 return new StaticMapping(uidMapping, gidMapping); 621 } 622 623 synchronized public int getUid(String user) throws IOException { 624 checkAndUpdateMaps(); 625 626 Integer id = uidNameMap.inverse().get(user); 627 if (id == null) { 628 updateMapIncr(user, false); 629 id = uidNameMap.inverse().get(user); 630 if (id == null) { 631 throw new IOException("User just deleted?:" + user); 632 } 633 } 634 return id.intValue(); 635 } 636 637 synchronized public int getGid(String group) throws IOException { 638 checkAndUpdateMaps(); 639 640 Integer id = gidNameMap.inverse().get(group); 641 if (id == null) { 642 updateMapIncr(group, true); 643 id = gidNameMap.inverse().get(group); 644 if (id == null) { 645 throw new IOException("No such group:" + group); 646 } 647 } 648 return id.intValue(); 649 } 650 651 synchronized public String getUserName(int uid, String unknown) { 652 checkAndUpdateMaps(); 653 String uname = uidNameMap.get(uid); 654 if (uname == null) { 655 try { 656 updateMapIncr(uid, false); 657 } catch (Exception e) { 658 } 659 uname = uidNameMap.get(uid); 660 if (uname == null) { 661 LOG.warn("Can't find user name for uid " + uid 662 + ". Use default user name " + unknown); 663 uname = unknown; 664 } 665 } 666 return uname; 667 } 668 669 synchronized public String getGroupName(int gid, String unknown) { 670 checkAndUpdateMaps(); 671 String gname = gidNameMap.get(gid); 672 if (gname == null) { 673 try { 674 updateMapIncr(gid, true); 675 } catch (Exception e) { 676 } 677 gname = gidNameMap.get(gid); 678 if (gname == null) { 679 LOG.warn("Can't find group name for gid " + gid 680 + ". Use default group name " + unknown); 681 gname = unknown; 682 } 683 } 684 return gname; 685 } 686 687 // When can't map user, return user name's string hashcode 688 public int getUidAllowingUnknown(String user) { 689 checkAndUpdateMaps(); 690 int uid; 691 try { 692 uid = getUid(user); 693 } catch (IOException e) { 694 uid = user.hashCode(); 695 LOG.info("Can't map user " + user + ". Use its string hashcode:" + uid); 696 } 697 return uid; 698 } 699 700 // When can't map group, return group name's string hashcode 701 public int getGidAllowingUnknown(String group) { 702 checkAndUpdateMaps(); 703 int gid; 704 try { 705 gid = getGid(group); 706 } catch (IOException e) { 707 gid = group.hashCode(); 708 LOG.info("Can't map group " + group + ". Use its string hashcode:" + gid); 709 } 710 return gid; 711 } 712}