/* * EncFS Java Library * Copyright (C) 2011 Mark R. Pariente * * This program 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 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 Lesser General Public License for more details. */ import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Stack; import java.util.StringTokenizer; import org.mrpdaemon.sec.encfs.EncFSConfig; import org.mrpdaemon.sec.encfs.EncFSConfigFactory; import org.mrpdaemon.sec.encfs.EncFSException; import org.mrpdaemon.sec.encfs.EncFSFile; import org.mrpdaemon.sec.encfs.EncFSFileInputStream; import org.mrpdaemon.sec.encfs.EncFSFileProvider; import org.mrpdaemon.sec.encfs.EncFSInvalidPasswordException; import org.mrpdaemon.sec.encfs.EncFSLocalFileProvider; import org.mrpdaemon.sec.encfs.EncFSProgressListener; import org.mrpdaemon.sec.encfs.EncFSUtil; import org.mrpdaemon.sec.encfs.EncFSVolume; import org.mrpdaemon.sec.encfs.EncFSVolumeBuilder; public final class EncFSShell { // EncFSFile stack representing the current directory path private static Stack<EncFSFile> dirStack = new Stack<>(); // EncFSFile representing the current directory private static EncFSFile curDir; // EncFSVolume that we're working on private static EncFSVolume volume; // Buffered reader for reading input stream private static BufferedReader br; private EncFSShell() { } // Search method that returns individual path elements for a given path private static List<EncFSFile> getPath(String path) throws IOException { List<EncFSFile> result = new ArrayList<>(); // Root directory handling if (path.equals(EncFSVolume.ROOT_PATH)) { result.add(volume.getRootDir()); return result; } EncFSFile curFile; boolean found; // Absolute vs. relative path handling if (path.startsWith(EncFSVolume.PATH_SEPARATOR)) { curFile = volume.getRootDir(); } else { curFile = curDir; } StringTokenizer st = new StringTokenizer(path, EncFSVolume.PATH_SEPARATOR); while (st.hasMoreTokens()) { String pathElement = st.nextToken(); found = false; if (curFile.isDirectory()) { EncFSFile[] files = curFile.listFiles(); for (EncFSFile file : files) { if (file.getName().equals(pathElement)) { result.add(file); curFile = file; found = true; } } } else { // Not a directory, better be the last token if (st.hasMoreTokens()) { throw new FileNotFoundException("'"+pathElement+"' is not a directory!"); } else { result.add(curFile); found = true; } } if (!found) { throw new FileNotFoundException("Path '"+path+"' not found!"); } } return result; } // Method for accepting password input private static String passwordInput() { System.out.print("Enter password: "); String password = null; try { password = br.readLine(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } return password; } // Method to create a new volume private static boolean createVolume(String path) { System.out.print("No EncFS volume found at '"+path+"' would you like to create it? [Yes/No]: "); String response; try { response = br.readLine(); } catch (IOException e) { e.printStackTrace(); return false; } if (response.equalsIgnoreCase("yes")||response.equalsIgnoreCase("y")) { // If the directory doesn't exist create it first File inputDir = new File(path); if (!inputDir.exists()) { if (!inputDir.mkdir()) { return true; } } String password = passwordInput(); // Create the volume try { EncFSFileProvider fileProvider = new EncFSLocalFileProvider(inputDir); EncFSConfig config = EncFSConfigFactory.createDefault(); // new // EncFSVolumeBuilder().withFileProvider(fileProvider).withConfig(config).withPassword(password).create(); new EncFSVolumeBuilder().withFileProvider(fileProvider).withConfig(config).withPassword(password).writeVolumeConfig(); } catch (Exception e) { e.printStackTrace(); return false; } System.out.println("New volume '"+path+"' created successfully."); // Open the volume try { volume = new EncFSVolumeBuilder().withRootPath(path).withPassword(password).buildVolume(); } catch (Exception e) { System.out.println(e.getMessage()); return false; } return true; } return false; } public static void main(String[] args) { if (args.length!=1) { System.out.println("This application takes one argument:"+" path to an EncFS volume"); System.exit(1); } br = new BufferedReader(new InputStreamReader(System.in)); /* * If the given directory or the config file doesn't exist ask for creation. */ File inputDir = new File(args[0]); File configFile = new File(args[0], EncFSVolume.CONFIG_FILE_NAME); if (!inputDir.exists()||!configFile.exists()) { if (!createVolume(args[0])) { System.exit(1); } } else { String password = passwordInput(); // Try to open the EncFSVolume at args[0] using the given password try { volume = new EncFSVolumeBuilder().withRootPath(args[0]).withPassword(password).buildVolume(); } catch (EncFSInvalidPasswordException e) { System.out.println("Invalid password!"); System.exit(1); } catch (EncFSException e) { System.out.println(e.getMessage()); System.exit(1); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); System.exit(1); } catch (IOException e) { System.out.println(e.getMessage()); System.exit(1); } } // Start at the root of the EncFS volume curDir = volume.getRootDir(); // Shell loop while (true) { try { // Print banner if (curDir==volume.getRootDir()) { System.out.print("/ > "); } else { if (curDir.getParentPath().equals(EncFSVolume.ROOT_PATH)) { System.out.print(EncFSVolume.ROOT_PATH+curDir.getName()+" > "); } else { System.out.print(EncFSVolume.combinePath(curDir.getParentPath(), curDir.getName())+" > "); } } // Read next command String inputBuffer = null; try { inputBuffer = br.readLine(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } // Tokenize the input line StringTokenizer st = new StringTokenizer(inputBuffer, " "); if (!st.hasMoreTokens()) { // Just ENTER or some spaces continue; } // Command processing String command = st.nextToken(); if ("ls".equals(command)) { // list child directories // Options class ListOptions { public boolean reverse = false; public boolean sortByTime = false; public boolean longListingFormat = false; } final ListOptions options = new ListOptions(); String pathStr = null; // Option parsing String token; while (st.hasMoreTokens()) { token = st.nextToken(); if (token.startsWith("-")) { // Option switches if (token.contains("l")) { options.longListingFormat = true; } else if (token.contains("r")) { options.reverse = true; } else if (token.contains("s")) { options.sortByTime = true; } } else { // Path specifier pathStr = readFileName(st, token); } } // Obtain list of files in the target directory EncFSFile[] files; if (pathStr==null) { files = curDir.listFiles(); } else { try { List<EncFSFile> pathList = getPath(pathStr); EncFSFile lastPathElement = pathList.get(pathList.size()-1); if (lastPathElement.isDirectory()) { files = lastPathElement.listFiles(); } else { System.out.println("'"+pathStr+"'"+" is not a directory!"); continue; } } catch (FileNotFoundException e) { System.out.println(e.getMessage()); continue; } } // Comparator implementation for sorting Comparator<EncFSFile> comparator = new Comparator<EncFSFile>() { public int compare(EncFSFile arg0, EncFSFile arg1) { int result; if (options.sortByTime) { long diff = arg0.getLastModified()-arg1.getLastModified(); if (diff>0) { result = -1; } else if (diff==0) { result = 0; } else { result = 1; } } else { result = arg0.getName().compareTo(arg1.getName()); } if (options.reverse) { result = -1*result; } return result; } }; // Sort files if needed if (options.reverse||options.sortByTime) { Arrays.sort(files, comparator); } // Print the listing for (EncFSFile file : files) { if (options.longListingFormat) { if (file.isDirectory()) { System.out.print("d"); } else { System.out.print("-"); } if (file.isReadable()) { System.out.print("r"); } else { System.out.print("-"); } if (file.isWritable()) { System.out.print("w"); } else { System.out.print("-"); } if (file.isExecutable()) { System.out.print("x"); } else { System.out.print("-"); } System.out.print("???"); System.out.print("???"); System.out.print(" "); String tmpSize = " "+file.getLength(); System.out.print(tmpSize.substring(tmpSize.length()-9)); System.out.print(" "); System.out.print(new Date(file.getLastModified())); System.out.print(" "); System.out.print(file.getName()); System.out.println(); } else { if (file.isDirectory()) { System.out.println(file.getName()+EncFSVolume.PATH_SEPARATOR); } else { System.out.println(file.getName()); } } } } else if ("mkdir".equals(command)||"mkdirs".equals(command)) { String dirPath = (st.hasMoreTokens() ? readFileName(st) : null); if (dirPath==null) { System.out.println("mkdir {dirname}"); continue; } boolean result; if (command.equals("mkdir")) { try { result = volume.makeDir(EncFSVolume.combinePath(curDir, dirPath)); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); continue; } } else { result = volume.makeDirs(EncFSVolume.combinePath(curDir, dirPath)); } if (!result) { System.out.println("Failed to create directory '"+dirPath+"'"); } } else if ("rm".equals(command)) { // remove String filePath = null; boolean recursive = false; // Options / path parsing while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.startsWith("-")) { if (token.contains("r")) { recursive = true; } } else { filePath = readFileName(st, token); } } if (filePath==null) { System.out.println("rm [-r] <filename>"); continue; } boolean result; try { result = volume.deletePath(EncFSVolume.combinePath(curDir, filePath), recursive, new EncFSShellProgressListener()); } catch (FileNotFoundException e) { System.out.println("File not found: '"+filePath+"'"); continue; } if (!result) { System.out.println("Failed to delete path '"+filePath+"'"); } } else if ("mv".equals(command)) { // move / rename int pathCount = 0; boolean force = false; String pathArray[] = new String[2]; // Option/path parsing while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.startsWith("-")) { if (token.contains("f")) { force = true; } } else { pathArray[pathCount++] = readFileName(st, token); } } if (pathCount<2) { System.out.println("Usage: mv [-f] <srcPath> <dstPath>"); continue; } List<EncFSFile> srcPathList; try { srcPathList = getPath(pathArray[0]); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); continue; } try { List<EncFSFile> dstPathList = getPath(pathArray[1]); EncFSFile lastPathElement = dstPathList.get(dstPathList.size()-1); /* * It is ok for the last path element to exist if it is a directory - in that case we'll just * move the source path into that directory */ if (!force&&!lastPathElement.isDirectory()) { System.out.println("Destination path '"+pathArray[1]+"' exists!"); continue; } } catch (FileNotFoundException e) { // This is expected without -f } String srcPath = srcPathList.get(srcPathList.size()-1).getPath(); // Need to convert destination path to an absolute path String dstPath = null; if (pathArray[1].startsWith(EncFSVolume.PATH_SEPARATOR)) { // Already an absolute path dstPath = pathArray[1]; } else { // Combine with current path dstPath = EncFSVolume.combinePath(curDir, pathArray[1]); } boolean result; try { result = volume.movePath(srcPath, dstPath, new EncFSShellProgressListener()); } catch (IOException e) { System.out.println(e.getMessage()); continue; } if (!result) { System.out.println("Failed to move '"+srcPath+"' to '"+dstPath+"'"); } } else if ("cp".equals(command)) { // copy a file or directory int pathCount = 0; boolean recursive = false; String pathArray[] = new String[2]; // Option/path parsing while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.startsWith("-")) { if (token.contains("r")) { recursive = true; } } else { pathArray[pathCount++] = readFileName(st, token); } } if (pathCount<2) { System.out.println("Usage: cp [-r] <srcPath> <dstPath>"); continue; } EncFSFile lastPathElement; List<EncFSFile> srcPathList; try { srcPathList = getPath(pathArray[0]); /* * If source path is a directory require recursive flag to proceed */ lastPathElement = srcPathList.get(srcPathList.size()-1); if (lastPathElement.isDirectory()) { if (!recursive) { System.out.println("Source path '"+pathArray[0]+"' is a directory. Use -r to copy."); continue; } } } catch (FileNotFoundException e) { System.out.println(e.getMessage()); continue; } String srcPath = srcPathList.get(srcPathList.size()-1).getPath(); // Need to convert destination path to an absolute path String dstPath; if (pathArray[1].startsWith(EncFSVolume.PATH_SEPARATOR)) { // Already an absolute path dstPath = pathArray[1]; } else { // Combine with current path dstPath = EncFSVolume.combinePath(curDir, pathArray[1]); } boolean result; try { result = volume.copyPath(srcPath, dstPath, new EncFSShellProgressListener()); } catch (IOException e) { System.out.println(e.getMessage()); continue; } if (!result) { System.out.println("Failed to copy '"+srcPath+"' to '"+dstPath+"'"); } } else if ("exit".equals(command)) { // bail out System.exit(0); } else if ("cd".equals(command)) { // go into a child directory if (!st.hasMoreTokens()) { System.out.println("No directory name specified"); continue; } String dirPath = readFileName(st); // .. handling if ("..".equals(dirPath)) { if (dirStack.empty()) { System.out.println("Can't go above root directory"); continue; } curDir = dirStack.pop(); // go back one level continue; } // '/' handling if (dirPath.equals(EncFSVolume.ROOT_PATH)) { dirStack.clear(); curDir = volume.getRootDir(); continue; } // regular directory path, find and cd into it List<EncFSFile> pathList; try { pathList = getPath(dirPath); } catch (FileNotFoundException e) { System.out.println("Path '"+dirPath+"' doesn't exist!"); continue; } // Make sure the last element is a directory EncFSFile lastPathElement = pathList.get(pathList.size()-1); if (!lastPathElement.isDirectory()) { System.out.println("'"+lastPathElement.getName()+"' is not a directory!"); continue; } /* * Current directory goes into the stack also Special handling for absolute paths */ if (dirPath.startsWith(EncFSVolume.PATH_SEPARATOR)) { // Clear the existing stack first dirStack.clear(); dirStack.push(volume.getRootDir()); } else { dirStack.push(curDir); } // Push all path elements except the last one into the stack Iterator<EncFSFile> itr = pathList.iterator(); while (itr.hasNext()) { EncFSFile dir = itr.next(); if (itr.hasNext()) { dirStack.push(dir); } } curDir = lastPathElement; } else if ("cat".equals(command)) { if (!st.hasMoreTokens()) { System.out.println("No file name specified"); continue; } String filePath = readFileName(st); // Find and print file List<EncFSFile> pathList; try { pathList = getPath(filePath); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); continue; } EncFSFile lastPathElement = pathList.get(pathList.size()-1); if (lastPathElement.isDirectory()) { System.out.println("'"+filePath+"' is not a file!"); continue; } EncFSUtil.copyWholeStreamAndCloseInput(new EncFSFileInputStream(lastPathElement), System.out); System.out.println(); } } catch (EncFSException e) { System.out.println(e.getMessage()); e.printStackTrace(); System.exit(1); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); e.printStackTrace(); System.exit(1); } catch (IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); System.exit(1); } } } static class EncFSShellProgressListener extends EncFSProgressListener { private int numProcessed = 0; @Override public void handleEvent(int eventType) { switch (eventType) { case EncFSProgressListener.FILES_COUNTED_EVENT: break; case EncFSProgressListener.NEW_FILE_EVENT: if (this.getNumFiles()!=0) { System.out.println("["+(numProcessed*100)/this.getNumFiles()+"%] Processing: "+this.getCurrentFile()); numProcessed++; } else { System.out.println("Processing: "+this.getCurrentFile()); } break; case EncFSProgressListener.FILE_PROCESS_EVENT: break; case EncFSProgressListener.OP_COMPLETE_EVENT: System.out.println("[100%] Operation complete!"); break; default: System.out.println("Unknown event type: "+eventType); break; } } } private static String readFileName(StringTokenizer st) { return readFileName(st, null); } // Read a path orfilename from StringTokenizer st - can include spaces is // quoted with "token1 token2 token3" // if token != null, this is the first token already read from st // if the path starts with ", read multiple tokens (separated by " // ") until the name ends with " or the last token is read. private static String readFileName(StringTokenizer st, String token) { String filePath = (token==null) ? st.nextToken() : token; if (filePath.startsWith("\"")) { filePath = filePath.substring(1); while (st.hasMoreTokens()) { filePath += " "+st.nextToken(); if (filePath.endsWith("\"")) { filePath = filePath.substring(0, filePath.length()-1); break; } } } return filePath; } }