/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.utils.executor.fileinfo; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import de.rcenvironment.core.toolkitbridge.transitional.TextStreamWatcherFactory; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.textstream.TextStreamWatcher; import de.rcenvironment.core.utils.common.textstream.receivers.CapturingTextOutReceiver; import de.rcenvironment.core.utils.executor.CommandLineExecutor; import de.rcenvironment.core.utils.executor.fileinfo.internal.AbstractFileService; /** * {@link FileInfoService} implementation for unix systems. * * @author Christian Weiss */ public class UnixFileInfoService extends AbstractFileService { private static final Pattern LS_DIRECTORY_PATTERN = Pattern.compile("^(.*):$"); private static final Pattern LS_TOTAL_PATTERN = Pattern.compile("^total \\d+$"); private static final Pattern LS_LINE_PATTERN = Pattern .compile("^([-dl])(?:[-r][-w][-xst]){3}(?:\\+)?\\s+\\d+\\s+([-_\\w]+)\\s+([-_\\w]+)\\s+(\\d+)\\s+(\\d+)\\s+(.*)\\s*$"); private static final Pattern FILE_CMD_DIRECTORY_PATTERN = Pattern.compile(".*: (?:sticky )?directory$"); private static final Pattern FILE_CMD_SYMLINK_PATTERN = Pattern.compile("^(.*): (?:broken )?symbolic link to `(.*)'$"); private static final Pattern NON_EXISTING_PATTERN = Pattern.compile("^.*: cannot open `.*' \\(No such file or directory\\)$"); private static final Pattern PERMISSION_DENIED_PATTERN = Pattern.compile("^.*: cannot access `.*': \\(?Permission denied\\)?$"); public UnixFileInfoService(final CommandLineExecutor commandLineExecutor) { super(commandLineExecutor); } @Override public Collection<FileInfo> listFiles(final String directory, final boolean recursively) throws IOException { return listContent(directory, recursively, false); } @Override public Collection<FileInfo> listContent(final String directory, final boolean recursively) throws IOException { return listContent(directory, recursively, true); } protected Collection<FileInfo> listContent(final String directory, final boolean recursively, final boolean directories) throws IOException { final List<FileInfo> result = new LinkedList<FileInfo>(); final String commandPattern; if (recursively) { commandPattern = "ls -R -l -A -b -q --time-style=+%%s --color=never %s"; } else { commandPattern = "ls -l -A -b -q --time-style=+%%s --color=never %s"; } final String command = StringUtils.format(commandPattern, directory); try { final Output executionOutput = exec(command); if (executionOutput.err.length() > 0) { throw new IOException(executionOutput.err); } final String output = executionOutput.out.trim(); try (final Scanner scanner = new Scanner(output)) { String relativePathPrefix = ""; while (scanner.hasNextLine()) { final String line = scanner.nextLine(); if (line.isEmpty()) { continue; } if (LS_TOTAL_PATTERN.matcher(line).matches()) { continue; } final Matcher directoryMatcher = LS_DIRECTORY_PATTERN.matcher(line); if (directoryMatcher.matches()) { relativePathPrefix = directoryMatcher.group(1); if (relativePathPrefix.equals(".")) { relativePathPrefix = ""; } relativePathPrefix = relativePathPrefix.replaceAll("\\./", ""); continue; } final FileInfo fileInfo = parseFileInfo(line, directory, relativePathPrefix); // e.g. broken links result in null if (fileInfo != null) { // exclude directories if desired if (!directories && fileInfo.isDirectory()) { continue; } result.add(fileInfo); } } } return result; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } } private FileInfo parseFileInfo(final String line, final String path, final String directory) throws IOException { final Matcher matcher = LS_LINE_PATTERN.matcher(line); if (!matcher.matches()) { throw new IOException(StringUtils.format("LS line does not match pattern: %s", line)); } // type of entry is first char {-, d, l} char type = matcher.group(1).charAt(0); // parse size long size; try { final String sizeString = matcher.group(4); size = Long.parseLong(sizeString); } catch (NumberFormatException e) { throw new IOException(e); } // parse modification date Date modificationDate = null; try { final String timestampString = matcher.group(5); final long timestamp = Long.parseLong(timestampString) * 1000L; modificationDate = new Date(timestamp); } catch (NumberFormatException e) { throw new IOException(e); } // parse entry name String name = matcher.group(6); // if the entry is a link the real type, size and modification time of // the linked file needs to be retrieved if (type == 'l') { // adjust the name to the part before the '-> <link target>' name = name.substring(0, name.indexOf("->") - 1); // resolve the real target path final String targetPath = resolveTarget(path + name); // if the link is broken return null if (targetPath == null) { return null; } if (isDirectory(targetPath)) { type = 'd'; } else { type = '-'; // only for files get their real size size = size(targetPath); // only for files get their modification time // TODO } } String relativePath; if (directory.isEmpty()) { relativePath = name; } else { relativePath = directory + '/' + name; } // directories are set to size 0 if (type == 'd') { size = 0; } // remove multiple slashes in path relativePath = relativePath.replaceAll("/+", "/"); final FileInfo fileInfo = new FileInfo(type == '-', relativePath, modificationDate, size); return fileInfo; } @Override public boolean isDirectory(final String path) throws IOException { final String commandPattern = "file %s"; final String command = StringUtils.format(commandPattern, path); try { final Output executionOutput = exec(command); if (executionOutput.err.length() > 0) { throw new IOException(executionOutput.err); } else { final String output = executionOutput.out.trim(); final Matcher matcher1 = FILE_CMD_DIRECTORY_PATTERN.matcher(output); if (matcher1.matches()) { return true; } else { final Matcher matcher2 = FILE_CMD_SYMLINK_PATTERN.matcher(output); if (matcher2.matches()) { return isDirectory(matcher2.group(2)); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } return false; } private String resolveTarget(final String path) throws IOException { final String commandPattern = "file %s"; final String command = StringUtils.format(commandPattern, path); try { final Output executionOutput = exec(command); if (executionOutput.err.length() > 0) { throw new IOException(executionOutput.err); } else { final String output = executionOutput.out.trim(); Matcher matcher = FILE_CMD_SYMLINK_PATTERN.matcher(output); if (matcher.matches()) { final String targetPath = matcher.group(2); if (targetPath.startsWith("/")) { return resolveTarget(targetPath); } else { final String absolutePath = path.substring(0, path.lastIndexOf('/') + 1) + targetPath; return resolveTarget(absolutePath); } } matcher = NON_EXISTING_PATTERN.matcher(output); if (matcher.matches()) { return null; } return path; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } } @Override public Long size(final String path) throws IOException { if (isDirectory(path)) { return 0L; } final String commandPattern = "du --bytes %s"; final String command = StringUtils.format(commandPattern, path); try { final Output executionOutput = exec(command); if (executionOutput.err.length() > 0) { if (PERMISSION_DENIED_PATTERN.matcher(executionOutput.err.trim()).matches()) { return null; } throw new IOException(executionOutput.err); } else { final String output = executionOutput.out.trim(); final String[] sizeInfo = output.split("\\s+"); return Long.parseLong(sizeInfo[0]); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } }; private Output exec(final String commandString) throws IOException, InterruptedException { final CommandLineExecutor executor = getCommandLineExecutor(); // set bash language executor.setEnv("LANG", "en_US.UTF8"); InputStream stdout; InputStream stderr; executor.start(commandString); stdout = executor.getStdout(); stderr = executor.getStderr(); final CapturingTextOutReceiver outReceiver = new CapturingTextOutReceiver(""); final CapturingTextOutReceiver errReceiver = new CapturingTextOutReceiver(""); final TextStreamWatcher stdoutWatcher = TextStreamWatcherFactory.create(stdout, outReceiver); final TextStreamWatcher stderrWatcher = TextStreamWatcherFactory.create(stderr, errReceiver); stdoutWatcher.start(); stderrWatcher.start(); executor.waitForTermination(); stdoutWatcher.waitForTermination(); stderrWatcher.waitForTermination(); final Output result = new Output(); result.out = outReceiver.getBufferedOutput(); result.err = errReceiver.getBufferedOutput(); return result; } /** * Information of output type. * * @author Christian Weiss */ private static final class Output { private String out; private String err; } }