/*
*
* Panbox - encryption for cloud storage
* Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additonally, third party code may be provided with notices and open source
* licenses from communities and third parties that govern the use of those
* portions, and any licenses granted hereunder do not alter any rights and
* obligations you may have under such open source licenses, however, the
* disclaimer of warranty and limitation of liability provisions of the GPLv3
* will apply to all the product.
*
*/
package org.panbox.desktop.common.vfs.backend.generic;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Hashtable;
import javax.crypto.SecretKey;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.panbox.core.crypto.io.AESGCMRandomAccessFileCompat;
import org.panbox.core.exception.FileEncryptionException;
import org.panbox.core.exception.FileIntegrityException;
import org.panbox.core.vfs.backend.VirtualFile;
import org.panbox.core.vfs.backend.VirtualVolume;
import org.panbox.desktop.common.ex.PanboxEncryptionException;
import org.panbox.desktop.common.ex.PanboxIntegrityException;
import org.panbox.desktop.common.vfs.backend.VirtualRandomAccessFile;
public class GenericVirtualFileImpl extends VirtualRandomAccessFile {
protected File file;
protected AESGCMRandomAccessFileCompat aesRandomAccessFile;
protected boolean deleteFileOnClose = false;
private final static Logger logger = Logger
.getLogger(GenericVirtualFileImpl.class);
/**
* stores number of open file handles for each opened file
*/
final static Hashtable<AESGCMRandomAccessFileCompat, Integer> filehandleCtrMap = new Hashtable<AESGCMRandomAccessFileCompat, Integer>();
protected GenericVirtualFileImpl(String fileName, VirtualVolume volume) {
super(fileName, volume);
file = new File(fileName);
try {
if (!isDirectory()) {
if (exists()) {
aesRandomAccessFile = file.canWrite() ? AESGCMRandomAccessFileCompat
.getInstance(file, true)
: AESGCMRandomAccessFileCompat.getInstance(file,
false);
// System.err.println("getInstance(" + file.getName() + ","
// + aesRandomAccessFile.writable + ") -> "
// + aesRandomAccessFile.toString());
} else {
aesRandomAccessFile = file.getParentFile().canWrite() ? AESGCMRandomAccessFileCompat
.getInstance(file, true)
: AESGCMRandomAccessFileCompat.getInstance(file,
false);
// System.err.println("getInstance(" + file.getName() + ","
// + aesRandomAccessFile.writable + ") -> "
// + aesRandomAccessFile.toString());
}
}
} catch (IOException | FileEncryptionException e) {
logger.error("Unable to initialize AES backend", e);
}
}
@Override
public boolean isDirectory() {
return file.isDirectory();
}
@Override
public boolean delete() {
if (file.isDirectory()) {
try {
FileUtils.deleteDirectory(file);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
} else {
return file.delete();
}
}
@Override
public boolean createNewDirectory() {
return file.mkdirs();
}
@Override
public boolean exists() {
return file.exists();
}
@Override
public long length() throws IOException {
// return aesRandomAccessFile.length();
return AESGCMRandomAccessFileCompat
.realToVirtualFileSize(file.length());
}
@Override
public VirtualFile[] list() {
String[] files = file.list();
GenericVirtualFileImpl[] fileList = new GenericVirtualFileImpl[files.length];
for (int i = 0; i < files.length; ++i) {
fileList[i] = new GenericVirtualFileImpl(
new File(file, files[i]).getPath(), volume);
}
return fileList;
}
@Override
public String getPath() {
return file.getPath();
}
@Override
public String getRelativePath() {
return volume.getRelativePathForFile(this);
}
@Override
public boolean renameTo(VirtualFile newFile) {
if (isDirectory()) {
return file.renameTo(newFile.getFile());
} else {
// use backend rename function
GenericVirtualFileImpl newDFile = (GenericVirtualFileImpl) newFile;
if (aesRandomAccessFile.renameTo(newDFile.getFile())) {
this.file = newDFile.getFile();
return true;
} else {
return false;
}
}
}
@Override
public File getFile() {
return file;
}
@Override
public String toString() {
return file.getName() + " at " + file.getAbsoluteFile();
}
@Override
public long getCreationTime() {
try {
BasicFileAttributes attr = Files.readAttributes(file.toPath(),
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
return attr.creationTime().toMillis();
} catch (IOException e) {
logger.error("Error in getCreationTime()", e);
return 0;
}
}
@Override
public long getLastAccessTime() {
try {
BasicFileAttributes attr = Files.readAttributes(file.toPath(),
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
return attr.lastAccessTime().toMillis();
} catch (IOException e) {
logger.error("Error in getLastAccessTime()", e);
return 0;
}
}
@Override
public long getLastWriteTime() {
// return file.lastModified();
try {
BasicFileAttributes attr = Files.readAttributes(file.toPath(),
BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
return attr.lastModifiedTime().toMillis();
} catch (IOException e) {
logger.error("Error in getLastWriteTime()", e);
return 0;
}
}
@Override
public void setLastAccessTime(long atime) throws IOException {
if (atime > 0) {
FileTime fileTime = FileTime.fromMillis(atime);
Files.setAttribute(file.toPath(), "lastAccessTime", fileTime);
}
}
@Override
public void setModifiedTime(long mtime) throws IOException {
if (mtime > 0) {
FileTime fileTime = FileTime.fromMillis(mtime);
Files.setLastModifiedTime(file.toPath(), fileTime);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#seek(long)
*/
@Override
public void seek(long pos) throws IOException, PanboxEncryptionException {
try {
aesRandomAccessFile.seek(pos);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#getFilePointer()
*/
@Override
public long getFilePointer() throws IOException {
try {
return aesRandomAccessFile.getFilePointer();
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#skipBytes(int)
*/
@Override
public int skipBytes(int n) throws IOException, PanboxEncryptionException {
try {
return aesRandomAccessFile.skipBytes(n);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#write(byte[])
*/
@Override
public void write(byte[] b) throws IOException, PanboxEncryptionException {
try {
aesRandomAccessFile.write(b);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#write(long,
* java.nio.ByteBuffer)
*/
@Override
public int write(long seekpos, ByteBuffer b) throws IOException,
PanboxEncryptionException {
try {
aesRandomAccessFile.seek(seekpos);
byte[] buf;
if (b.hasArray()) {
buf = b.array();
this.write(buf, 0, buf.length);
} else {
buf = new byte[b.remaining()];
b.get(buf);
this.write(buf, 0, buf.length);
}
return buf.length;
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#write(long,
* java.nio.ByteBuffer)
*/
@Override
public int write(long seekpos, byte[] b) throws IOException,
PanboxEncryptionException {
try {
aesRandomAccessFile.seek(seekpos);
this.write(b, 0, b.length);
return b.length;
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#write(byte[], int,
* int)
*/
@Override
public void write(byte[] b, int off, int len) throws IOException,
PanboxEncryptionException {
try {
aesRandomAccessFile.write(b, off, len);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#write(int)
*/
@Override
public void write(int b) throws IOException, PanboxEncryptionException {
try {
aesRandomAccessFile.write(b);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#read(byte[], int,
* int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException,
PanboxEncryptionException {
try {
return aesRandomAccessFile.read(b, off, len);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#read(byte[])
*/
@Override
public int read(byte[] b) throws IOException, PanboxEncryptionException {
try {
return aesRandomAccessFile.read(b);
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#read(long,
* java.nio.ByteBuffer)
*/
@Override
public int read(long seekpos, ByteBuffer b) throws IOException,
PanboxEncryptionException {
byte[] buf = new byte[b.remaining()]; // raf.array() does not
// work, because backend
// byte[] is missing!
try {
aesRandomAccessFile.seek(seekpos);
int reallyRead = aesRandomAccessFile.read(buf, 0, buf.length);
if (reallyRead != -1) {
// Trim NUL-Bytes from file
b.put(buf, 0, reallyRead);
return reallyRead;
} else {
// EOF is indicated by -1; returning -1 will be misinterpreted
// as an ERRNO (i.e. EPERM)
return 0;
}
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#read(long,
* java.nio.ByteBuffer)
*/
@Override
public int read(long seekpos, byte[] b) throws IOException,
PanboxEncryptionException {
try {
aesRandomAccessFile.seek(seekpos);
int reallyRead = aesRandomAccessFile.read(b);
if (reallyRead == -1) {
// EOF is indicated by -1; returning -1 will be misinterpreted
// as an ERRNO (i.e. EPERM)
return 0;
} else {
return reallyRead;
}
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#read()
*/
@Override
public int read() throws IOException, PanboxEncryptionException {
try {
return aesRandomAccessFile.read();
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#open(java.io.File,
* boolean)
*/
@Override
public void open() throws IOException, PanboxEncryptionException {
try {
// windows verifies file existance by calling create(OPEN_EXISTING)
// - thus check if file exists at an earlypoint in time
if (!file.exists()) {
throw new IOException("File " + file + "does not exist!");
} else {
// TODO: support read-only mode, use fileinfo-objects
// check file handle counter maps and create/increase values
synchronized (filehandleCtrMap) {
Integer fhctr = filehandleCtrMap.get(aesRandomAccessFile);
// System.err.println("open(" + file.getName() + ","
// + aesRandomAccessFile.writable + ", ctr=" + fhctr
// + ") -> " + aesRandomAccessFile.toString());
if (fhctr != null) {
// file has already been opened - only increase counter
filehandleCtrMap.put(aesRandomAccessFile, ++fhctr);
} else {
aesRandomAccessFile.open();
filehandleCtrMap.put(aesRandomAccessFile, 1);
}
}
}
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#create(int,
* javax.crypto.SecretKey, java.io.File)
*/
@Override
public void create(int shareKeyVersion, SecretKey shareKey)
throws IOException, PanboxEncryptionException {
try {
// don't check if file has been opened - multiple create() calls on
// the same file should cause an exception
// TODO: check error handling + mapping to error codes
aesRandomAccessFile.create(shareKeyVersion, shareKey);
// System.err.println("create(" + file.getName() + ","
// + aesRandomAccessFile.writable + ") -> "
// + aesRandomAccessFile.toString());
// check file handle counter maps and create/increase values
synchronized (filehandleCtrMap) {
Integer fhctr = filehandleCtrMap.get(aesRandomAccessFile);
if (fhctr == null) {
filehandleCtrMap.put(aesRandomAccessFile, 1);
}
}
} catch (IOException e) {
throw new IOException(e.getMessage(), e);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#flush()
*/
@Override
public void flush() throws IOException {
aesRandomAccessFile.flush();
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#close()
*/
@Override
public void close() throws IOException {
if (!file.exists()) {
throw new IOException("File " + file + "does not exist!");
} else {
synchronized (filehandleCtrMap) {
Integer fhctr = filehandleCtrMap.get(aesRandomAccessFile);
if ((fhctr == null) || (fhctr < 1)) {
throw new IOException(
"Invalid value of file handle counter!");
} else {
if (fhctr == 1) {
// last reference, close file and remove entry
aesRandomAccessFile.close();
// System.err.println("close(" + file.getName() + ","
// + aesRandomAccessFile.writable + ") -> "
// + aesRandomAccessFile.toString());
filehandleCtrMap.remove(aesRandomAccessFile);
if(deleteFileOnClose) {
delete();
}
} else if (fhctr > 1) {
// decrease counter value, but don't close file
filehandleCtrMap.put(aesRandomAccessFile, --fhctr);
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#setDeleteOnClose()
*/
@Override
public void setDeleteOnClose() {
deleteFileOnClose = true;
}
/*
* (non-Javadoc)
*
* @see
* org.panbox.desktop.common.vfs.backend.VirtualFile#getShareKeyVersion()
*/
@Override
public int getShareKeyVersion() throws PanboxEncryptionException,
IOException {
try {
return aesRandomAccessFile.getShareKeyVersion();
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see
* org.panbox.desktop.common.vfs.backend.VirtualFile#initWithShareKey(javax
* .crypto .SecretKey)
*/
@Override
public void initWithShareKey(SecretKey shareKey)
throws PanboxEncryptionException, IOException {
try {
aesRandomAccessFile.initWithShareKey(shareKey);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
*
* @see org.panbox.desktop.common.vfs.backend.VirtualFile#setLength(long)
*/
@Override
public void setLength(long newLength) throws IOException,
PanboxEncryptionException {
try {
aesRandomAccessFile.setLength(newLength);
} catch (FileEncryptionException e) {
throw new PanboxEncryptionException(e.getMessage(), e);
} catch (FileIntegrityException e) {
throw new PanboxIntegrityException(e.getMessage(), e);
}
}
@Override
public boolean isOpened() {
return aesRandomAccessFile.isOpen();
}
@Override
public boolean isSymbolic() {
return Files.isSymbolicLink(getFile().toPath());
}
@Override
public void setAttr(long attr) throws IOException {
super.setAttr(attr);
}
@Override
public boolean canExecute() {
return file.canExecute();
}
@Override
public boolean canRead() {
return file.canRead();
}
@Override
public boolean canWrite() {
return file.canWrite();
}
}