|
@@ -18,8 +18,14 @@
|
|
|
package org.apache.hadoop.nfs.nfs3;
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileInputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStreamReader;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
@@ -44,6 +50,17 @@ public class IdUserGroup {
|
|
|
static final String MAC_GET_ALL_USERS_CMD = "dscl . -list /Users UniqueID";
|
|
|
static final String MAC_GET_ALL_GROUPS_CMD = "dscl . -list /Groups PrimaryGroupID";
|
|
|
|
|
|
+ // Used for finding the configured static mapping file.
|
|
|
+ static final String NFS_STATIC_MAPPING_FILE_KEY = "dfs.nfs.static.mapping.file";
|
|
|
+ private static final String NFS_STATIC_MAPPING_FILE_DEFAULT = "/etc/nfs.map";
|
|
|
+ private final File staticMappingFile;
|
|
|
+
|
|
|
+ // 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+(\\d+)\\s*(#.*)?$");
|
|
|
+
|
|
|
// Do update every 15 minutes by default
|
|
|
final static long TIMEOUT_DEFAULT = 15 * 60 * 1000; // ms
|
|
|
final static long TIMEOUT_MIN = 1 * 60 * 1000; // ms
|
|
@@ -58,6 +75,7 @@ public class IdUserGroup {
|
|
|
|
|
|
public IdUserGroup() throws IOException {
|
|
|
timeout = TIMEOUT_DEFAULT;
|
|
|
+ staticMappingFile = new File(NFS_STATIC_MAPPING_FILE_DEFAULT);
|
|
|
updateMaps();
|
|
|
}
|
|
|
|
|
@@ -71,6 +89,11 @@ public class IdUserGroup {
|
|
|
} else {
|
|
|
timeout = updateTime;
|
|
|
}
|
|
|
+
|
|
|
+ String staticFilePath = conf.get(NFS_STATIC_MAPPING_FILE_KEY,
|
|
|
+ NFS_STATIC_MAPPING_FILE_DEFAULT);
|
|
|
+ staticMappingFile = new File(staticFilePath);
|
|
|
+
|
|
|
updateMaps();
|
|
|
}
|
|
|
|
|
@@ -137,7 +160,8 @@ public class IdUserGroup {
|
|
|
*/
|
|
|
@VisibleForTesting
|
|
|
public static void updateMapInternal(BiMap<Integer, String> map, String mapName,
|
|
|
- String command, String regex) throws IOException {
|
|
|
+ String command, String regex, Map<Integer, Integer> staticMapping)
|
|
|
+ throws IOException {
|
|
|
BufferedReader br = null;
|
|
|
try {
|
|
|
Process process = Runtime.getRuntime().exec(
|
|
@@ -151,7 +175,7 @@ public class IdUserGroup {
|
|
|
}
|
|
|
LOG.debug("add to " + mapName + "map:" + nameId[0] + " id:" + nameId[1]);
|
|
|
// HDFS can't differentiate duplicate names with simple authentication
|
|
|
- final Integer key = parseId(nameId[1]);
|
|
|
+ final Integer key = staticMapping.get(parseId(nameId[1]));
|
|
|
final String value = nameId[0];
|
|
|
if (map.containsKey(key)) {
|
|
|
final String prevValue = map.get(key);
|
|
@@ -173,7 +197,7 @@ public class IdUserGroup {
|
|
|
}
|
|
|
map.put(key, value);
|
|
|
}
|
|
|
- LOG.info("Updated " + mapName + " map size:" + map.size());
|
|
|
+ LOG.info("Updated " + mapName + " map size: " + map.size());
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
LOG.error("Can't update " + mapName + " map");
|
|
@@ -199,20 +223,115 @@ public class IdUserGroup {
|
|
|
+ " 'nobody' will be used for any user and group.");
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ StaticMapping staticMapping = new StaticMapping(
|
|
|
+ new HashMap<Integer, Integer>(), new HashMap<Integer, Integer>());
|
|
|
+ if (staticMappingFile.exists()) {
|
|
|
+ LOG.info("Using '" + staticMappingFile + "' for static UID/GID mapping...");
|
|
|
+ staticMapping = parseStaticMap(staticMappingFile);
|
|
|
+ } else {
|
|
|
+ LOG.info("Not doing static UID/GID mapping because '" + staticMappingFile
|
|
|
+ + "' does not exist.");
|
|
|
+ }
|
|
|
|
|
|
if (OS.startsWith("Linux")) {
|
|
|
- updateMapInternal(uMap, "user", LINUX_GET_ALL_USERS_CMD, ":");
|
|
|
- updateMapInternal(gMap, "group", LINUX_GET_ALL_GROUPS_CMD, ":");
|
|
|
+ updateMapInternal(uMap, "user", LINUX_GET_ALL_USERS_CMD, ":",
|
|
|
+ staticMapping.uidMapping);
|
|
|
+ updateMapInternal(gMap, "group", LINUX_GET_ALL_GROUPS_CMD, ":",
|
|
|
+ staticMapping.gidMapping);
|
|
|
} else {
|
|
|
// Mac
|
|
|
- updateMapInternal(uMap, "user", MAC_GET_ALL_USERS_CMD, "\\s+");
|
|
|
- updateMapInternal(gMap, "group", MAC_GET_ALL_GROUPS_CMD, "\\s+");
|
|
|
+ updateMapInternal(uMap, "user", MAC_GET_ALL_USERS_CMD, "\\s+",
|
|
|
+ staticMapping.uidMapping);
|
|
|
+ updateMapInternal(gMap, "group", MAC_GET_ALL_GROUPS_CMD, "\\s+",
|
|
|
+ staticMapping.gidMapping);
|
|
|
}
|
|
|
|
|
|
uidNameMap = uMap;
|
|
|
gidNameMap = gMap;
|
|
|
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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)));
|
|
|
+
|
|
|
+ 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);
|
|
|
+ int remoteId = Integer.parseInt(lineMatcher.group(2));
|
|
|
+ int localId = Integer.parseInt(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();
|