/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.protocol.nfs.nfs2; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.URL; import java.util.List; import java.util.StringTokenizer; import org.jnode.net.nfs.Protocol; import org.jnode.net.nfs.nfs2.FileAttribute; import org.jnode.net.nfs.nfs2.LookupResult; import org.jnode.net.nfs.nfs2.NFS2Client; import org.jnode.net.nfs.nfs2.NFS2Exception; import org.jnode.net.nfs.nfs2.ReadFileResult; import org.jnode.net.nfs.nfs2.mount.ExportEntry; import org.jnode.net.nfs.nfs2.mount.Mount1Client; import org.jnode.net.nfs.nfs2.mount.MountException; import org.jnode.net.nfs.nfs2.mount.MountResult; /** * A NFS2InputStream obtains the bytes from a nfs2 connection. * The URL is nfs://host/remotePath * The remotePath contains also the export path. * * @author Andrei Dore */ public class NFS2InputStream extends InputStream { private static int DEFAULT_BUFFER_SIZE = NFS2Client.MAX_DATA; private byte[] buffer; private int bufferCount; private int bufferPosition; private long markFileOffset = -1; private int markLimit = -1; private Mount1Client mountClient; private NFS2Client nfsClient; private String mountDirectory; private long fileOffset; private byte[] fileHandle; private FileAttribute fileAttribute; public NFS2InputStream(URL url) throws IOException { // FIXME ... exception handling in this method should be reviewed. At the very least, // there are places where finally clauses should be used. mountClient = new Mount1Client(InetAddress.getByName(url.getHost()), Protocol.TCP, -1, -1); nfsClient = new NFS2Client(InetAddress.getByName(url.getHost()), Protocol.TCP, -1, -1); String path = url.getPath(); List<ExportEntry> exportList; try { exportList = mountClient.export(); } catch (MountException e) { try { mountClient.close(); } catch (IOException e1) { // ignore } throw new IOException(e.getMessage()); } catch (IOException e) { try { mountClient.close(); } catch (IOException e1) { // ignore } throw e; } ExportEntry exportEntry = null; for (ExportEntry e : exportList) { if (path.startsWith(e.getDirectory())) { if (exportEntry == null) { exportEntry = e; } else { if (exportEntry.getDirectory().length() < e.getDirectory().length()) { exportEntry = e; } } } } if (exportEntry == null) { throw new IOException("The path " + path + " it is not exported"); } mountDirectory = exportEntry.getDirectory(); MountResult mountResult; try { mountResult = mountClient.mount(mountDirectory); } catch (MountException e) { try { mountClient.close(); } catch (IOException e1) { // ignore } throw new IOException(e.getMessage()); } catch (IOException e) { try { mountClient.close(); } catch (IOException e1) { // ignore } throw e; } byte[] tempFileHandle = mountResult.getFileHandle(); try { String filePath = path.substring(exportEntry.getDirectory().length()); StringTokenizer tokenizer = new StringTokenizer(filePath, "/"); while (tokenizer.hasMoreElements()) { String t = tokenizer.nextToken(); LookupResult lookup = nfsClient.lookup(tempFileHandle, t); if (lookup.getFileAttribute().getType() == FileAttribute.FILE) { fileHandle = lookup.getFileHandle(); fileAttribute = lookup.getFileAttribute(); break; } else if (lookup.getFileAttribute().getType() == FileAttribute.DIRECTORY) { tempFileHandle = lookup.getFileHandle(); } else { throw new IOException("The path contains an unknow resource: " + t + ". It is not directory or file"); } } } catch (NFS2Exception e) { try { close(); } catch (IOException e1) { // ignore } throw new IOException(e.getMessage()); } catch (IOException e) { try { close(); } catch (IOException e1) { // ignore } throw e; } if (fileHandle == null) { throw new IOException("The target of the " + url.toString() + " it is not a file."); } buffer = new byte[DEFAULT_BUFFER_SIZE]; } @Override public synchronized int read() throws IOException { if (bufferPosition >= bufferCount) { if (fillBuffer() == 0) { return -1; } } int data = buffer[bufferPosition] & 0xFF; bufferPosition++; return data; } @Override public synchronized int read(byte[] b, int off, int len) throws IOException { if (len == 0) { return 0; } int readBytes = 0; while (true) { if (readBytes == len) { return readBytes; } int avail = bufferCount - bufferPosition; if (avail == 0) { if (fillBuffer() == 0) { if (readBytes == 0) { return -1; } return readBytes; } } int count = Math.min(len - readBytes, bufferCount - bufferPosition); System.arraycopy(buffer, bufferPosition, b, off + readBytes, count); readBytes += count; bufferPosition += count; } } @Override public synchronized int available() throws IOException { return bufferCount - bufferPosition; } private int fillBuffer() throws IOException { if (fileOffset >= fileAttribute.getSize()) { return 0; } ReadFileResult readFileResult; try { readFileResult = nfsClient.readFile(fileHandle, (int) fileOffset, DEFAULT_BUFFER_SIZE); } catch (NFS2Exception e) { throw new IOException(e.getMessage()); } fileAttribute = readFileResult.getFileAttribute(); fileOffset += readFileResult.getData().length; System.arraycopy(readFileResult.getData(), 0, buffer, 0, readFileResult.getData().length); bufferPosition = 0; bufferCount = readFileResult.getData().length; return bufferCount; } @Override public synchronized void mark(int readlimit) { this.markLimit = readlimit; this.markFileOffset = fileOffset; } @Override public boolean markSupported() { return true; } @Override public synchronized void reset() throws IOException { if (markFileOffset == -1 && markLimit == -1) { throw new IOException("The mark was not set. Use mark method to set the mark"); } if (fileOffset - markFileOffset > markLimit) { throw new IOException("The mark limit exced."); } fileOffset = markFileOffset; // reset the buffer bufferPosition = 0; bufferCount = 0; // TODO Optimize this. If the mark it is buffer don't reset the buffer. // Unfortunately it is not a simple modification in this method. } public synchronized long skip(long n) throws IOException { if (n <= 0) { return 0; } if (n < bufferCount - bufferPosition) { // It is inside of the buffer bufferPosition += n; return n; } else { // It is outside of the buffer: reset the buffer bufferCount = 0; bufferPosition = 0; if (fileOffset + n - (bufferCount - bufferPosition) < fileAttribute.getSize()) { fileOffset += n - (bufferCount - bufferPosition); return n; } else { long skipBytes = fileAttribute.getSize() - fileOffset; fileOffset = fileAttribute.getSize(); return skipBytes; } } } // TODO Remove the synch in the future @Override public synchronized void close() throws IOException { if (mountClient != null) { try { mountClient.unmount(mountDirectory); } catch (MountException e) { // FIXME ... ignore? } try { mountClient.close(); } catch (IOException e) { // FIXME ... ignore? } } if (nfsClient != null) { try { nfsClient.close(); } catch (IOException e) { // FIXME ... ignore? } } } }