Commit 9d25fffb authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

nfs: added nfs v3 and v4.1 servers

parent f85aff62
/*
* $Id:ChimeraNFSException.java 140 2007-06-07 13:44:55Z tigran $
*/
package org.dcache.chimera.nfs;
public class ChimeraNFSException extends java.io.IOException {
/**
*
*/
private static final long serialVersionUID = 4319461664218810541L;
private final int nfsStatus;
public ChimeraNFSException(int newStatus, String msg) {
super(msg);
nfsStatus = newStatus;
}
public int getStatus() {
return nfsStatus;
}
}
package org.dcache.chimera.nfs;
public class ExportClient {
public enum Root {
TRUSTED, NOTTRUSTED
}
public enum IO {
RW, RO
}
private final String _ip;
private final Root _isTrusted;
private final IO _rw;
public ExportClient(String ip, Root isTrusted, IO rw) {
_ip = ip;
_isTrusted = isTrusted;
_rw = rw;
}
public String ip() {
return _ip;
}
public IO io() {
return _rw;
}
public Root trusted() {
return _isTrusted;
}
}
package org.dcache.chimera.nfs;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.dcache.chimera.nfs.ExportClient.IO;
import org.dcache.chimera.nfs.ExportClient.Root;
public class ExportFile {
private final Map<String, FsExport> _exports ;
/**
* Create a new empty exports list
*/
public ExportFile() {
_exports = new HashMap<String, FsExport>();
}
public ExportFile(File file) throws IOException {
_exports = parse(file);
}
public List<String> getExports() {
return new ArrayList<String>(_exports.keySet());
}
private static Map<String, FsExport> parse(File exportFile) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(exportFile));
Map<String, FsExport> exports = new HashMap<String, FsExport>();
String line;
try {
int lineCount = 0;
while ((line = br.readLine()) != null) {
++lineCount;
line = line.trim();
if (line.length() == 0)
continue;
if (line.charAt(0) == '#')
continue;
FsExport export = null;
StringTokenizer st = new StringTokenizer(line);
String path = st.nextToken();
if( st.hasMoreTokens() ) {
List<ExportClient> clients = new ArrayList<ExportClient>();
while(st.hasMoreTokens() ) {
String hostAndOptions = st.nextToken();
StringTokenizer optionsTokenizer = new StringTokenizer(hostAndOptions, "(),");
String host = optionsTokenizer.nextToken();
Root isTrusted = ExportClient.Root.NOTTRUSTED;
IO rw = ExportClient.IO.RO;
while(optionsTokenizer.hasMoreTokens()) {
String option = optionsTokenizer.nextToken();
if( option.equals("rw") ) {
rw = ExportClient.IO.RW;
continue;
}
if( option.equals("no_root_squash") ) {
isTrusted = ExportClient.Root.TRUSTED;
continue;
}
}
ExportClient client = new ExportClient(host,isTrusted, rw );
clients.add(client);
}
export = new FsExport(path, clients);
}else{
ExportClient everyOne = new ExportClient("*",ExportClient.Root.NOTTRUSTED, ExportClient.IO.RO );
List<ExportClient> clients = new ArrayList<ExportClient>(1);
clients.add(everyOne);
export = new FsExport(path, clients );
}
exports.put(path, export);
}
} finally {
try {
br.close();
} catch (IOException dummy) {
// ignored
}
}
return exports;
}
public FsExport getExport(String path) {
return _exports.get(path);
}
// FIXME: one trusted client has an access to all tree
public boolean isTrusted( java.net.InetAddress client ){
List<String> exports = getExports();
for( String path: exports ) {
FsExport fsExport = getExport(path);
if( fsExport.isTrusted(client) ) {
return true;
}
}
return false;
}
/**
* add a new export to existing exports
*
* @param export
*/
public void addExport( FsExport export) {
_exports.put(export.getPath(), export);
}
}
package org.dcache.chimera.nfs;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import org.dcache.chimera.nfs.ExportClient.Root;
public class FsExport {
private final String _path;
private final List<ExportClient> _clients = new ArrayList<ExportClient>();
/**
* NFS clients may be specified in a number of ways:<br>
* <p>
*
* <b>single host</b>
* <p>
* This is the most common format. You may specify a host either by an
* abbreviated name recognized be the resolver, the fully qualified domain
* name, or an IP address.
* <p>
*
* <b>wildcards</b>
* <p>
* Machine names may contain the wildcard characters * and ?. This can be
* used to make the exports file more compact; for instance, .cs.foo.edu
* matches all hosts in the domain cs.foo.edu. As these characters also
* match the dots in a domain name, the given pattern will also match all
* hosts within any subdomain of cs.foo.edu.
* <p>
*
* <b>IP networks</b>
* <p>
* You can also export directories to all hosts on an IP (sub-) network
* simultaneously. This is done by specifying an IP address and netmask pair
* as address/netmask where the netmask can be specified in dotted-decimal
* format, or as a contiguous mask length (for example, either
* `/255.255.252.0' or `/22' appended to the network base address result in
* identical subnetworks with 10 bits of host). Wildcard characters
* generally do not work on IP addresses, though they may work by accident
* when reverse DNS lookups fail.
* <p>
*
*
* @param path
* @param clients list of {@link ExportClient} which allowed to mount this export.
*/
public FsExport(String path, List<ExportClient> clients) {
_path = path;
_clients.addAll(clients);
}
public String getPath() {
return _path;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(_path).append(":");
if (_clients.isEmpty()) {
sb.append(" *");
} else {
for (ExportClient client : _clients) {
sb.append(" ").append(client.ip()).append("(").append(
client.io()).append(",").append(client.trusted())
.append(")");
}
}
return sb.toString();
}
public boolean isAllowed(InetAddress client) {
// localhost always allowed
if( client.isLoopbackAddress() ) {
return true;
}else{
for (ExportClient exportClient : _clients) {
if( IPMatcher.match(exportClient.ip(), client) ) {
return true;
}
}
}
return false;
}
public boolean isTrusted(InetAddress client) {
// localhost always allowed
if( client.isLoopbackAddress() ) {
return true;
}else{
for (ExportClient exportClient : _clients) {
if( exportClient.trusted() == Root.TRUSTED && IPMatcher.match(exportClient.ip(), client)) {
return true;
}
}
}
return false;
}
public List<String> client() {
List<String> client = new ArrayList<String>(_clients.size());
for (ExportClient exportClient : _clients) {
client.add(exportClient.ip());
}
return client;
}
}
package org.dcache.chimera.nfs;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IPMatcher {
// FIXME: make it more elegant
public static boolean match(String pattern, InetAddress ip) {
if( pattern.indexOf('*') != -1 || pattern.indexOf('?') != -1) {
// regexp
String hostName = ip.getHostName();
return match(pattern, hostName);
}else{
// ip
try {
return match(InetAddress.getByName(pattern), ip, 32);
}catch(UnknownHostException uhe) {
return false;
}
}
}
public static boolean match(String pattern, String hostName ) {
Pattern p = Pattern.compile(toRegExp(pattern));
Matcher m = p.matcher(hostName);
return m.matches();
}
/**
* Checks matching ip in specified subnet.
*
* @param ip address to test
* @param subnet address
* @param mask netmask
* @return true if ip matches subnet.
*/
public static boolean match( InetAddress ip, InetAddress subnet, int mask ) {
byte[] ipBytes = ip.getAddress();
byte[] netBytes = subnet.getAddress();
int ipLong = 0;
int netLong = 0;
// create an integer from address bytes
ipLong |= (255 & ipBytes[0]);
ipLong <<= 8;
ipLong |= (255 & ipBytes[1]);
ipLong <<= 8;
ipLong |= (255 & ipBytes[2]);
ipLong <<= 8;
ipLong |= (255 & ipBytes[3]);
netLong |= (255 & netBytes[0]);
netLong <<= 8;
netLong |= (255 & netBytes[1]);
netLong <<= 8;
netLong |= (255 & netBytes[2]);
netLong <<= 8;
netLong |= (255 & netBytes[3]);
// first mask bits from left should be the same
ipLong = ipLong >> (32 - mask);
netLong = netLong >> (32 - mask);
return ipLong == netLong ;
}
private static String toRegExp(String s) {
return s.replace(".", "\\.").replace("?", ".").replace("*", ".*");
}
}
/*
* $Id:NFSHandle.java 140 2007-06-07 13:44:55Z tigran $
*/
package org.dcache.chimera.nfs;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dcache.chimera.FileSystemProvider;
import org.dcache.chimera.FsInode;
import org.dcache.chimera.FsInodeType;
import org.dcache.chimera.FsInode_CONST;
import org.dcache.chimera.FsInode_ID;
import org.dcache.chimera.FsInode_NAMEOF;
import org.dcache.chimera.FsInode_PARENT;
import org.dcache.chimera.FsInode_PATHOF;
import org.dcache.chimera.FsInode_PGET;
import org.dcache.chimera.FsInode_PSET;
import org.dcache.chimera.FsInode_TAG;
import org.dcache.chimera.FsInode_TAGS;
public class NFSHandle {
private static final Logger _log = Logger.getLogger(NFSHandle.class.getName());
private NFSHandle() {
// no instance allowed
}
public static FsInode toFsInode(FileSystemProvider fs, byte[] handle) {
FsInode inode = null;
String strHandle = new String(handle);
_log.log(Level.FINEST, "Processing FH: {0}", strHandle );
StringTokenizer st = new StringTokenizer(strHandle, "[:]");
if (st.countTokens() < 3) {
throw new IllegalArgumentException("Invalid HimeraNFS handler.("
+ strHandle + ")");
}
/*
* reserved for future use
*/
int fsId = Integer.parseInt(st.nextToken());
String type = st.nextToken();
try {
// IllegalArgumentException will be thrown is it's wrong type
FsInodeType inodeType = FsInodeType.valueOf(type);
String id;
int argc;
String[] args;
switch (inodeType) {
case INODE:
id = st.nextToken();
int level = 0;
if (st.countTokens() > 0) {
level = Integer.parseInt(st.nextToken());
}
inode = new FsInode(fs, id, level);
break;
case ID:
id = st.nextToken();
inode = new FsInode_ID(fs, id);
break;
case TAGS:
id = st.nextToken();
inode = new FsInode_TAGS(fs, id);
break;
case TAG:
id = st.nextToken();
String tag = st.nextToken();
inode = new FsInode_TAG(fs, id, tag);
break;
case NAMEOF:
id = st.nextToken();
inode = new FsInode_NAMEOF(fs, id);
break;
case PARENT:
id = st.nextToken();
inode = new FsInode_PARENT(fs, id);
break;
case PATHOF:
id = st.nextToken();
inode = new FsInode_PATHOF(fs, id);
break;
case CONST:
String cnst = st.nextToken();
inode = new FsInode_CONST(fs, cnst);
break;
case PSET:
id = st.nextToken();
argc = st.countTokens();
args = new String[argc];
for (int i = 0; i < argc; i++) {
args[i] = st.nextToken();
}
inode = new FsInode_PSET(fs, id, args);
break;
case PGET:
id = st.nextToken();
argc = st.countTokens();
args = new String[argc];
for (int i = 0; i < argc; i++) {
args[i] = st.nextToken();
}
inode = new FsInode_PGET(fs, id, args);
break;
}
} catch (IllegalArgumentException iae) {
_log.log(Level.INFO, "Failed to generate an inode from file handle : {0} : {1}",
new Object[] {strHandle, iae});
inode = null;
}
return inode;
}
}
/*
* $Id:HimeraNfsUtils.java 140 2007-06-07 13:44:55Z tigran $
*/
package org.dcache.chimera.nfs.v3;
import org.dcache.chimera.nfs.v3.xdr.specdata3;
import org.dcache.chimera.nfs.v3.xdr.nfsstat3;
import org.dcache.chimera.nfs.v3.xdr.uint64;
import org.dcache.chimera.nfs.v3.xdr.uid3;
import org.dcache.chimera.nfs.v3.xdr.gid3;
import org.dcache.chimera.nfs.v3.xdr.fattr3;
import org.dcache.chimera.nfs.v3.xdr.nfstime3;
import org.dcache.chimera.nfs.v3.xdr.fileid3;
import org.dcache.chimera.nfs.v3.xdr.uint32;
import org.dcache.chimera.nfs.v3.xdr.mode3;
import org.dcache.chimera.nfs.v3.xdr.sattr3;
import org.dcache.chimera.nfs.v3.xdr.ftype3;
import org.dcache.chimera.nfs.v3.xdr.size3;
import org.dcache.chimera.nfs.v3.xdr.time_how;
import org.dcache.chimera.nfs.v3.xdr.wcc_attr;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.dcache.chimera.FsInode;
import org.dcache.chimera.ChimeraFsException;
import org.dcache.chimera.UnixPermission;
import org.dcache.xdr.RpcAuthType;
import org.dcache.xdr.RpcAuthTypeUnix;
import org.dcache.xdr.RpcCall;
public class HimeraNfsUtils {