/*
* $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.fs.service.def;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.jnode.java.io.VMFileSystemAPI;
import java.io.VMOpenMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import org.jnode.driver.Device;
import org.jnode.fs.FSAccessRights;
import org.jnode.fs.FSDirectory;
import org.jnode.fs.FSEntry;
import org.jnode.fs.FileSystem;
import org.jnode.java.io.VMFileHandle;
/**
* @author epr
* @author Yves Galante (yves.galante@jmob.net)
*/
final class FileSystemAPIImpl implements VMFileSystemAPI {
/** My logger */
private static final Logger log = Logger.getLogger(FileSystemAPIImpl.class);
/** My filesystem manager */
@SuppressWarnings("unused")
private final FileSystemManager fsm;
/** The path to entry cache */
private final FSEntryCache entryCache;
/** The open file handle manager */
private final FileHandleManager fhm;
/** The virtual filesystem */
private final VirtualFS vfs;
/**
* a map (fullPath -> FileSystem) of mount points
*/
private final Map<String, FileSystem<?>> mountPoints;
/**
* Create a new instance
*
* @param fsm
* @throws IOException
*/
public FileSystemAPIImpl(FileSystemManager fsm, VirtualFS vfs) {
this.fsm = fsm;
this.entryCache = new FSEntryCache();
this.fhm = new FileHandleManager();
this.vfs = vfs;
this.mountPoints = new HashMap<String, FileSystem<?>>();
}
/**
* Does the given file exist?
*/
public boolean fileExists(String file) {
final FSEntry entry = getEntry(file);
return (entry != null);
}
/**
* Is the given File a plain file?
*/
public boolean isFile(String file) {
final FSEntry entry = getEntry(file);
return (entry != null) && (entry.isFile());
}
/**
* Is the given File a directory?
*/
public boolean isDirectory(String file) {
final FSEntry entry = getEntry(file);
return (entry != null) && (entry.isDirectory());
}
/**
* Can the given file be read?
*
* @param file
*/
public boolean canRead(String file) throws IOException {
final FSAccessRights rights = getAccessRights(file);
return (rights == null) || rights.canRead();
}
/**
* Can the given file be written to?
*
* @param file
*/
public boolean canWrite(String file) throws IOException {
final FSAccessRights rights = getAccessRights(file);
return (rights == null) || rights.canWrite();
}
/**
* Can the given file be executed to?
*
* @param file
*/
public boolean canExecute(String file) throws IOException {
final FSAccessRights rights = getAccessRights(file);
return (rights == null) || rights.canExecute();
}
public boolean setReadable(String file, boolean enable, boolean owneronly) throws IOException {
final FSAccessRights rights = getAccessRights(file);
if (rights == null) {
return false;
}
return rights.setReadable(enable, owneronly);
}
public boolean setWritable(String file, boolean enable, boolean owneronly) throws IOException {
final FSAccessRights rights = getAccessRights(file);
if (rights == null) {
return false;
}
return rights.setWritable(enable, owneronly);
}
public boolean setExecutable(String file, boolean enable, boolean owneronly) throws IOException {
final FSAccessRights rights = getAccessRights(file);
if (rights == null) {
return false;
}
return rights.setExecutable(enable, owneronly);
}
/**
* Gets the length in bytes of the given file or 0 if the file does not
* exist.
*
* @param file
*/
public long getLength(String file) {
final FSEntry entry = getEntry(file);
if (entry != null) {
if (entry.isFile()) {
try {
return entry.getFile().getLength();
} catch (IOException ex) {
log.debug("Error in getLength", ex);
return 0;
}
} else {
log.debug("Not a file in getLength");
return 0;
}
} else {
log.debug("File not found in getLength (" + file + ")");
return 0;
}
}
/**
* Gets the last modification date of the given file.
*
* @param file
*/
public long getLastModified(String file) {
final FSEntry entry = getEntry(file);
if (entry != null) {
try {
return entry.getLastModified();
} catch (IOException ex) {
return 0;
}
} else {
return 0;
}
}
/**
* Sets the last modification date of the given file.
*
* @param file
*/
public void setLastModified(String file, long time) throws IOException {
final FSEntry entry = getEntry(file);
if (entry != null) {
entry.setLastModified(time);
} else {
throw new FileNotFoundException(file);
}
}
/**
* Mark the given file as readonly.
*
* @param file
* @throws IOException
*/
public void setReadOnly(String file) throws IOException {
setReadable(file, true, true);
setWritable(file, false, true);
setExecutable(file, false, true);
}
/**
* Delete the given file.
*
* @param file
* @throws IOException
*/
public void delete(String file) throws IOException {
final FSDirectory parentDirectory = getParentDirectoryEntry(file);
if (parentDirectory == null) {
throw new IOException("Parent of " + file + " not found");
}
parentDirectory.remove(getName(file));
entryCache.removeEntries(file);
}
/**
* This method returns an array of filesystem roots.
*/
public File[] getRoots() {
return new File[] {new File("/")};
}
/**
* Gets an array of names of all entries of the given directory. All names
* are relative to the given directory.
*
* @param directory
*/
public String[] list(String directory) throws IOException {
final FSEntry entry = getEntry(directory);
if (entry == null) {
throw new FileNotFoundException(directory);
}
if (!entry.isDirectory()) {
throw new IOException("Cannot list on non-directories " + directory);
}
final ArrayList<String> list = new ArrayList<String>();
final StringBuilder entryPath = new StringBuilder(directory).append(File.separatorChar);
final int directoryPathSize = entryPath.length();
for (Iterator<? extends FSEntry> i = entry.getDirectory().iterator(); i.hasNext();) {
final FSEntry child = i.next();
final String name = child.getName();
// never include the parent directory and the current directory in
// the result if they exist by any chance.
if (".".equals(name) || "..".equals(name)) {
continue;
}
entryPath.append(name);
entryCache.setEntry(entryPath.toString(), child);
entryPath.setLength(directoryPathSize);
if (name != null) {
list.add(name);
}
}
return list.toArray(new String[list.size()]);
}
private FSAccessRights getAccessRights(String path) throws IOException {
FSEntry entry = getEntry(path);
if (entry == null)
throw new FileNotFoundException("file not found: " + path);
try {
return entry.getAccessRights();
} catch (UnsupportedOperationException e) {
// todo review
// this feature is not implemented yet in al file system
// implementations
// return null in those cases
return null;
}
}
/**
* Gets the FSEntry for the given path, or null if not found.
*
* @param path must be an absolute canonical path
*/
private FSEntry getEntry(String path) {
try {
if (path == null) {
return null;
}
final int pathLen = path.length();
if (pathLen > 0) {
if ((path.charAt(0) == File.separatorChar) ||
(path.charAt(pathLen - 1) == File.separatorChar)) {
throw new IllegalArgumentException("Invalid path: " + path);
}
}
if (path.length() == 0) {
return vfs.getRootEntry();
}
FSEntry entry = entryCache.getEntry(path);
if (entry != null) {
return entry;
}
final FSDirectory parentEntry = getParentDirectoryEntry(path);
if (parentEntry != null) {
try {
entry = parentEntry.getEntry(stripParentPath(path));
if (entry == null) {
return null;
}
entryCache.setEntry(path, entry);
return entry;
} catch (IOException ex) {
// Not found
log.debug("parent.getEntry failed", ex);
return null;
}
} else {
return null;
}
} catch (IOException e) {
log.debug("Filesystem.getEntry failed", e);
return null;
}
}
/**
* Open a given file
*
* @param file absolute path
* @throws IOException
*/
public VMFileHandle open(String file, VMOpenMode mode) throws IOException {
FSEntry entry = getEntry(file);
if ((entry != null) && !entry.isFile()) {
throw new IOException("Not a file " + file);
}
if (entry == null) {
if (mode.canWrite()) {
// Try to create the file
FSDirectory parent = getParentDirectoryEntry(file);
if (parent == null) {
throw new IOException("Cannot create " + file +
", parent directory does not exist");
}
// Ok, add the file
entry = parent.addFile(getName(file));
} else {
throw new FileNotFoundException(file);
}
}
return fhm.open(entry.getFile(), mode);
// TODO open need not create the file but throw FileNotFoundException
}
/**
* Make a directory
*
* @param file
* @throws IOException
*/
public boolean mkDir(String file) throws IOException {
FSEntry entry = getEntry(file);
if (entry != null) {
log.debug(file + "exists");
return false;
}
FSDirectory directory = getParentDirectoryEntry(file);
if (directory == null) {
return false;
}
// Ok, add the dir
entry = directory.addDirectory(getName(file));
return true;
}
/**
* Make a file
*
* @param file
* @throws IOException
*/
public boolean mkFile(String file, VMOpenMode mode) throws IOException {
FSEntry entry = getEntry(file);
if ((entry != null) || !mode.canWrite()) {
return false;
}
FSDirectory directory = getParentDirectoryEntry(file);
if (directory == null)
return false;
// Ok, make the file
entry = directory.addFile(getName(file));
return true;
}
/**
* Mount the given filesystem at the fullPath, using the fsPath as root of
* the to be mounted filesystem.
*
* @param fullPath
* @param fs
* @param fsPath Null or empty to use the root of the filesystem.
*/
void mount(String fullPath, FileSystem<?> fs, String fsPath) throws IOException {
final String dir = getParentPath(fullPath);
final String name = stripParentPath(fullPath);
final FSEntry entry = getEntry(dir);
if (entry == null) {
throw new FileNotFoundException(dir);
}
if (!(entry instanceof VirtualDirEntry)) {
throw new IOException("Cannot mount at " + dir);
}
final VirtualDirEntry vde = (VirtualDirEntry) entry;
vde.addMount(name, fs, fsPath);
// transform fullPath to an absolute path
if (fullPath.charAt(0) != File.separatorChar) {
fullPath = File.separatorChar + fullPath;
}
mountPoints.put(fullPath, fs); // TODO handle removal (+ add unmount
// method) of filesystems
}
/**
* Return a map (fullPath -> FileSystem) of mount points
*
* @return a copy of the internal map, sorted by fullPath
*/
Map<String, FileSystem<?>> getMountPoints() {
return new TreeMap<String, FileSystem<?>>(mountPoints);
}
/**
* Is the given directory a mount.
*
* @param fullPath
* @return
*/
boolean isMount(String fullPath) {
final FSEntry entry = getEntry(fullPath);
return (entry instanceof VirtualMountEntry);
}
/**
* The filesystem on the given device will be removed.
*
* @param dev
*/
final void unregisterFileSystem(Device dev) {
vfs.unregisterFileSystem(dev);
}
/**
* Get the parent entry of a file
*
* @param file absolute path
* @return the directory entry, null if not exist or not a directory
* @throws IOException
*/
private FSDirectory getParentDirectoryEntry(String file) throws IOException {
if (file == null) {
return null;
}
final FSEntry dirEntry = getEntry(getParentPath(file));
if (dirEntry == null) {
return null;
}
if (!dirEntry.isDirectory()) {
return null;
}
return dirEntry.getDirectory();
}
/**
* @param path
* @return
*/
private String getName(String path) {
if (path == null) {
return null;
}
int idx = path.lastIndexOf(File.separatorChar);
return (idx >= 0) ? path.substring(idx + 1) : path;
}
/**
* Gets the parent path of the given path
*
* @param path
* @return
*/
private String getParentPath(String path) {
if (path == null) {
return null;
} else {
final int idx = path.lastIndexOf(File.separatorChar);
return (idx >= 0) ? path.substring(0, idx) : "";
}
}
/**
* Gets the given path without the parent path
*
* @param path
* @return
*/
private String stripParentPath(String path) {
if (path == null) {
return null;
} else {
final int idx = path.lastIndexOf(File.separatorChar);
return (idx >= 0) ? path.substring(idx + 1) : path;
}
}
public long getTotalSpace(String path) throws IOException {
final FSEntry entry = getEntry(path);
long length = 0L;
if (entry != null) {
length = entry.getFileSystem().getTotalSpace();
}
return length;
}
public long getFreeSpace(String path) throws IOException {
final FSEntry entry = getEntry(path);
long length = 0L;
if (entry != null) {
length = entry.getFileSystem().getFreeSpace();
}
return length;
}
public long getUsableSpace(String path) throws IOException {
final FSEntry entry = getEntry(path);
long length = 0L;
if (entry != null) {
length = entry.getFileSystem().getUsableSpace();
}
return length;
}
}