/* * * 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; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicLong; import net.fusejna.DirectoryFiller; import net.fusejna.ErrorCodes; import net.fusejna.FlockCommand; import net.fusejna.FuseException; import net.fusejna.StructFlock.FlockWrapper; import net.fusejna.StructFuseFileInfo.FileInfoWrapper; import net.fusejna.StructFuseFileInfo.FileInfoWrapper.OpenMode; import net.fusejna.StructStat.StatWrapper; import net.fusejna.StructStatvfs.StatvfsWrapper; import net.fusejna.StructTimeBuffer.TimeBufferWrapper; import net.fusejna.types.TypeMode.ModeWrapper; import net.fusejna.types.TypeMode.NodeType; import net.fusejna.util.FuseFilesystemAdapterFull; import org.apache.commons.io.FilenameUtils; import org.apache.log4j.Logger; import org.panbox.Settings; import org.panbox.core.exception.ObfuscationException; import org.panbox.desktop.common.ex.PanboxCreateFailedException; import org.panbox.desktop.common.ex.PanboxDeleteFailedException; import org.panbox.desktop.common.ex.PanboxEncryptionException; import org.panbox.desktop.common.ex.PanboxHandleException; import org.panbox.desktop.common.ex.PanboxRenameFailedException; import org.panbox.desktop.common.vfs.PanboxFSLinux.AccessMode; import org.panbox.desktop.common.vfs.backend.exceptions.SecretKeyNotFoundException; import org.panbox.desktop.linux.Utils; public final class FuseUserFS extends FuseFilesystemAdapterFull implements PanboxFSAdapter { private static final Logger logger = Logger .getLogger("org.panbox.desktop.common"); private static final ResourceBundle bundle = ResourceBundle.getBundle( "org.panbox.desktop.common.gui.Messages", Settings.getInstance() .getLocale()); private static final long fakeBlockSize = 4096L; private PanboxFSLinux panboxFS = null; public FuseUserFS() { } private String[] vfsoptions = null; public FuseUserFS(String[] vfsoptions) { this.vfsoptions = vfsoptions; } @Override protected String[] getOptions() { // per default, set some extra fuse mount options to allow to deviate // from the default write-buffersize of 4096 bytes for increasing // performance String[] ret = null; if ((vfsoptions == null) || (vfsoptions.length == 0)) { ret = new String[] { "-o", "big_writes", "-o", "max_write=4194304" }; logger.info("Using default FUSE options: " + Arrays.toString(ret)); } else { ret = vfsoptions; logger.info("FUSE options: " + Arrays.toString(ret)); } return ret; } /** * {@link AtomicLong} instance for file handle generation. */ final AtomicLong handleCtr = new AtomicLong(5); // private long setFH(FileInfoWrapper info) { // info.fh(handleCtr.incrementAndGet()); // return info.fh(); // } private final synchronized void log_error(Exception e) { logger.error(getMethodName(1) + ": Caught exception " + e.getClass().getSimpleName() + " from PanboxFS.", e); } public static final int R_OK = 4; /* test for read permission */ public static final int W_OK = 2; /* test for write permission */ public static final int X_OK = 1; /* test for execute (search) permission */ public static final int F_OK = 0; /* test for presence of file */ @Override public int access(String path, int access) { try { if (!panboxFS.access(path, AccessMode.EXISTS)) { return -ErrorCodes.ENOENT(); } else { if ((access & R_OK) == R_OK) { if (!panboxFS.access(path, AccessMode.READ)) { return -ErrorCodes.EACCES(); } } if ((access & W_OK) == W_OK) { if (!panboxFS.access(path, AccessMode.WRITE)) { return -ErrorCodes.EACCES(); } } if ((access & X_OK) == X_OK) { if (!panboxFS.access(path, AccessMode.EXECUTE)) { return -ErrorCodes.EACCES(); } } } } catch (FileNotFoundException | ObfuscationException e) { return -ErrorCodes.ENOENT(); } return 0; } /** * Get the method name for a depth in call stack. <br /> * Utility function. See <a href= * "http://stackoverflow.com/questions/442747/getting-the-name-of-the-current-executing-method" * >http://stackoverflow.com/questions/442747/getting-the-name-of-the- * current-executing-method</a> for details * * @param depth * depth in the call stack (0 means current method, 1 means call * method, ...) * @return method name */ public static String getMethodName(final int depth) { final StackTraceElement[] ste = Thread.currentThread().getStackTrace(); return ste[ste.length - 1 - depth].getMethodName(); } @Override public int create(final String path, final ModeWrapper mode, final FileInfoWrapper info) { try { if (FilenameUtils.indexOfLastSeparator(path) == 0) { // we're in the VFS root VFSErrorMessages.showErrorMessage( bundle.getString("FuseUserFS.creatingInRootDir"), bundle.getString("FuseUserFS.illegalOperation")); return -ErrorCodes.ECANCELED(); } else { // preparations boolean readonly = (info.openMode() == OpenMode.READONLY); long handle = handleCtr.incrementAndGet(); panboxFS.create(path, handle, readonly); // if no exception was thrown, set handle for subsequent session info.fh(handle); } } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (PanboxEncryptionException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeEncrypted"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } return 0; } @Override public void destroy() { panboxFS._onUnmount(); } @Override public int flush(final String path, final FileInfoWrapper info) { try { panboxFS.flush(path, info.fh()); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (PanboxHandleException e) { logger.error( "FuseUserFS::flush : Caught exception PanboxHandleException from PanboxFS: ", e); return -ErrorCodes.EIO(); } return 0; } final static int uid = Utils.getUid(); final static int gid = Utils.getGid(); @Override public int getattr(final String path, final StatWrapper stat) { try { if (!UnconsequentialFiles.isUnconsequential(path)) { FileInfo info = (FileInfo) panboxFS.getattr(path, false, false); if (info.isSymbolic()) { stat.setMode(NodeType.SYMBOLIC_LINK, info.getAttr()).size( info.getSize()); } else { stat.setMode( info.isDirectory() ? NodeType.DIRECTORY : NodeType.FILE, info.getAttr()).size( info.getSize()); } // defaults to current uid/gid. on rare occasions, FuseJNA // uid/gid resultion seems to fail stat.uid(uid).gid(gid); stat.setTimes(info.getLastAccessTime(), 0, info.getLastWriteTime(), 0, info.getCreationTime(), 0); } else { return -ErrorCodes.ENOENT(); // TODO: for dolphin bug with .directory.lock use this // return -ErrorCodes.EACCES(); } } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { // method is being used for file existance checking - omit logging // here // log_error(e); return -ErrorCodes.ENOENT(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } return 0; } @Override protected String getName() { return panboxFS.getFilesystemName() + "-" + panboxFS.getVolumeName(); } @Override public int mkdir(final String path, final ModeWrapper mode) { if (!UnconsequentialFiles.isUnconsequential(path)) { try { if (FilenameUtils.indexOfLastSeparator(path) == 0) { // we're in the VFS root VFSErrorMessages.showErrorMessage( bundle.getString("FuseUserFS.creatingInRootDir"), bundle.getString("FuseUserFS.illegalOperation")); return -ErrorCodes.EACCES(); } else { panboxFS.mkdir(path); } } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (PanboxCreateFailedException e) { log_error(e); return -ErrorCodes.EACCES(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages .showErrorMessage( MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } } return 0; } @Override public int open(final String path, final FileInfoWrapper info) { try { boolean readonly = (info.openMode() == OpenMode.READONLY); // shareModeDeletable always false for linux as files are deletable // after being opened by default long handle = handleCtr.incrementAndGet(); panboxFS.open(path, handle, readonly); info.fh(handle); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (PanboxEncryptionException e) { log_error(e); VFSErrorMessages.showErrorMessage( MessageFormat.format(bundle .getString("FuseUserFS.decryptionOfFileFailed"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } return 0; } @Override public int read(final String path, final ByteBuffer buffer, final long size, final long offset, final FileInfoWrapper info) { try { return panboxFS.read(path, buffer, offset, size, info.fh()); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (PanboxEncryptionException e) { log_error(e); VFSErrorMessages.showErrorMessage( MessageFormat.format(bundle .getString("FuseUserFS.decryptionOfFileFailed"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (PanboxHandleException e) { log_error(e); return -ErrorCodes.EIO(); } } @Override public int readdir(final String path, final DirectoryFiller filler) { try { if (!UnconsequentialFiles.isUnconsequential(path)) { Collection<String> files = panboxFS.readdir(path); for (Iterator<String> iterator = files.iterator(); iterator .hasNext();) { String curfile = (String) iterator.next(); try { AbstractFileInfo info = panboxFS.getattr(curfile, true, false); if (!filler.add(info.fileName)) { return -ErrorCodes.ENOMEM(); } } catch (SecretKeyNotFoundException e) { log_error(e); continue; } catch (FileNotFoundException e) { log_error(e); continue; } catch (IOException e) { log_error(e); continue; } } } } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } return 0; } @Override public int release(final String path, final FileInfoWrapper info) { try { // panboxFS._closeFile(path, info.fh()); panboxFS.release(path, info.fh()); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (PanboxHandleException e) { log_error(e); return -ErrorCodes.EIO(); } return 0; } @Override public int utimens(String path, TimeBufferWrapper wrapper) { try { panboxFS.setLastAccessTime(path, FileInfo.unixLong2JavaLong(wrapper.ac_sec())); panboxFS.setModifiedTime(path, FileInfo.unixLong2JavaLong(wrapper.mod_sec())); } catch (IOException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.ENOENT(); } return 0; } @Override public int rename(final String path, final String newName) { try { panboxFS.rename(path, newName); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (PanboxRenameFailedException e) { log_error(e); return -ErrorCodes.EEXIST(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (IOException e) { log_error(e); return -ErrorCodes.EACCES(); } return 0; } @Override public int rmdir(final String path) { try { panboxFS.rmdir(path); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } return 0; } @Override public int statfs(final String path, final StatvfsWrapper wrapper) { // NOTE: as the panbox VFS is not a real fs on one single physical // storage volume, but may reference multiple shares with differing // storage backend paths, we are unable to determine a sensible overall // number of available/total bytes. wrapper.setBlockInfo(panboxFS.getFreeBytes() / fakeBlockSize, panboxFS.getUsableBytes() / fakeBlockSize, panboxFS.getTotalBytes() / fakeBlockSize).setSizes( fakeBlockSize, 0); return 0; } @Override public int truncate(final String path, final long offset) { try { panboxFS.truncate(path, offset); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (PanboxEncryptionException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeEncrypted"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } return 0; } @Override public int unlink(final String path) { try { panboxFS.unlink(path); } catch (PanboxDeleteFailedException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } return 0; } @Override public boolean userfs_mount(final PanboxFS panboxFS, final File mountPoint) { return userfs_mount(panboxFS, mountPoint, true); } public boolean userfs_mount(final PanboxFS panboxFS, final File mountPoint, final boolean blocking) { if (!(panboxFS instanceof PanboxFSLinux)) { logger.fatal("FUSE implementation requires an instance of PanboxFSLinux!"); return false; } else { this.panboxFS = (PanboxFSLinux) panboxFS; try { mount(mountPoint, blocking, panboxFS); } catch (final FuseException e) { return false; } return true; } } @Override public boolean userfs_unmount(final File mountPoint) { try { unmount(); } catch (final Exception e) { return false; } return true; } @Override public int write(final String path, final ByteBuffer buf, final long bufSize, final long writeOffset, final FileInfoWrapper info) { try { return panboxFS.write(path, buf, writeOffset, bufSize, info.fh()); } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (PanboxEncryptionException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeEncrypted"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } catch (IOException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (PanboxHandleException e) { log_error(e); return -ErrorCodes.EIO(); } } @Override public int chmod(final String path, final ModeWrapper mode) { try { panboxFS.chmod(path, mode.getBits()); return 0; } catch (SecretKeyNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (FileNotFoundException e) { log_error(e); return -ErrorCodes.ENOENT(); } catch (IOException e) { log_error(e); return -ErrorCodes.EACCES(); } catch (ObfuscationException e) { log_error(e); VFSErrorMessages.showErrorMessage(MessageFormat.format( bundle.getString("FuseUserFS.fileCouldNotBeObfuscated"), path), bundle.getString("error")); return -ErrorCodes.EIO(); } } @Override public int lock(String path, FileInfoWrapper info, FlockCommand command, FlockWrapper flock) { // NOTE: libreoffice applications rely on fcntl(2) NOT returning ENOSYS. // See // http://fuse.sourceforge.net/doxygen/structfuse__operations.html#a1c3fff5cf0c1c2003d117e764b9a76fd // TODO: Actual implementation, should any applications break due to // missing locking mechanism return 0; } @Override public AbstractFileInfo createFileInfo(String fileName, boolean b, long i, long creationTime, long lastAccessTime, long lastWriteTime, long attr, boolean symbolic) { return new FileInfo(fileName, b, i, creationTime, lastAccessTime, lastWriteTime, attr, symbolic); } @Override public int symlink(String path, String target) { try { panboxFS.symlink(path, target); return 0; } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } } @Override public int readlink(String path, ByteBuffer buffer, long size) { try { panboxFS.readlink(path, buffer, size); return 0; } catch (IOException e) { log_error(e); return -ErrorCodes.EIO(); } } @Override public void beforeUnmount(File mountPoint) { try { super.beforeUnmount(mountPoint); panboxFS.beforeUnmount(mountPoint); } catch (Exception e) { log_error(e); } } }