This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 1ffbbf2023f90ef8a6f8d63f70d810a7a3f43633 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Mon Jun 8 10:05:34 2020 +0200 [SSHD-1009] Fix users home on OSX and support for symlinks --- .../file/nativefs/NativeFileSystemFactory.java | 2 +- .../java/org/apache/sshd/common/util/OsUtils.java | 52 ++++++++++++-------- .../org/apache/sshd/common/util/OsUtilsTest.java | 24 +++++++--- .../java/org/apache/sshd/server/scp/ScpShell.java | 56 ++++++++++++++++------ 4 files changed, 93 insertions(+), 41 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java index 784fc94..6d61105 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/file/nativefs/NativeFileSystemFactory.java @@ -41,7 +41,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean; * @author <a href="http://mina.apache.org">Apache MINA Project</a> */ public class NativeFileSystemFactory extends AbstractLoggingBean implements FileSystemFactory { - public static final String DEFAULT_USERS_HOME = OsUtils.isWin32() ? "C:\\Users" : "/home"; + public static final String DEFAULT_USERS_HOME = OsUtils.isWin32() ? "C:\\Users" : OsUtils.isOSX() ? "/Users" : "/home"; public static final NativeFileSystemFactory INSTANCE = new NativeFileSystemFactory(); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java index 2f5d8f9..182afe0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java @@ -60,7 +60,7 @@ public final class OsUtils { private static final AtomicReference<String> CURRENT_USER_HOLDER = new AtomicReference<>(null); private static final AtomicReference<VersionInfo> JAVA_VERSION_HOLDER = new AtomicReference<>(null); - private static final AtomicReference<Boolean> OS_TYPE_HOLDER = new AtomicReference<>(null); + private static final AtomicReference<String> OS_TYPE_HOLDER = new AtomicReference<>(null); private OsUtils() { throw new UnsupportedOperationException("No instance allowed"); @@ -70,42 +70,56 @@ public final class OsUtils { * @return true if the host is a UNIX system (and not Windows). */ public static boolean isUNIX() { - return !isWin32(); + return !isWin32() && !isOSX(); + } + + /** + * @return true if the host is a OSX (and not Windows or Unix). + */ + public static boolean isOSX() { + return getOS().contains("mac"); } /** * @return true if the host is Windows (and not UNIX). * @see #OS_TYPE_OVERRIDE_PROP - * @see #setWin32(Boolean) + * @see #setOS(String) */ public static boolean isWin32() { - Boolean typeValue; + return getOS().contains("windows"); + } + + /** + * Can be used to enforce Win32 or Linux report from {@link #isWin32()}, {@link #isOSX()} or {@link #isUNIX()} + * + * @param os The value to set - if {@code null} then O/S type is auto-detected + * @see #isWin32() + * @see #isOSX() + * @see #isUNIX() + */ + public static void setOS(String os) { + synchronized (OS_TYPE_HOLDER) { + OS_TYPE_HOLDER.set(os); + } + } + + /** + * Retrieve the OS + */ + private static String getOS() { + String typeValue; synchronized (OS_TYPE_HOLDER) { typeValue = OS_TYPE_HOLDER.get(); if (typeValue != null) { // is it the 1st time return typeValue; } - String value = System.getProperty(OS_TYPE_OVERRIDE_PROP, System.getProperty("os.name")); - typeValue = GenericUtils.trimToEmpty(value).toLowerCase().contains("windows"); + typeValue = GenericUtils.trimToEmpty(value).toLowerCase(); OS_TYPE_HOLDER.set(typeValue); } - return typeValue; } - /** - * Can be used to enforce Win32 or Linux report from {@link #isWin32()} or {@link #isUNIX()} - * - * @param win32 The value to set - if {@code null} then O/S type is auto-detected - * @see #isWin32() - */ - public static void setWin32(Boolean win32) { - synchronized (OS_TYPE_HOLDER) { - OS_TYPE_HOLDER.set(win32); - } - } - public static String resolveDefaultInteractiveShellCommand() { return resolveDefaultInteractiveShellCommand(isWin32()); } diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/OsUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/OsUtilsTest.java index 3fb66a5..5470d0f 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/util/OsUtilsTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/util/OsUtilsTest.java @@ -42,7 +42,7 @@ public class OsUtilsTest extends JUnitTestSupport { public void testSetOsTypeByProperty() { try { for (String osType : new String[] { "Some-Windows", "Some-Linux" }) { - OsUtils.setWin32(null); // force re-detection + OsUtils.setOS(null); // force re-detection try { boolean expected = osType.contains("Windows"); @@ -54,19 +54,29 @@ public class OsUtilsTest extends JUnitTestSupport { } } } finally { - OsUtils.setWin32(null); // force re-detection + OsUtils.setOS(null); // force re-detection } } @Test public void testSetOsTypeProgrammatically() { try { - for (boolean expected : new boolean[] { true, false }) { - OsUtils.setWin32(expected); // force value - assertEquals("Mismatched detection value", expected, OsUtils.isWin32()); - } + OsUtils.setOS("windows 10"); + assertEquals("Mismatched detection value", false, OsUtils.isOSX()); + assertEquals("Mismatched detection value", false, OsUtils.isUNIX()); + assertEquals("Mismatched detection value", true, OsUtils.isWin32()); + + OsUtils.setOS("mac os"); + assertEquals("Mismatched detection value", true, OsUtils.isOSX()); + assertEquals("Mismatched detection value", false, OsUtils.isUNIX()); + assertEquals("Mismatched detection value", false, OsUtils.isWin32()); + + OsUtils.setOS("linux"); + assertEquals("Mismatched detection value", false, OsUtils.isOSX()); + assertEquals("Mismatched detection value", true, OsUtils.isUNIX()); + assertEquals("Mismatched detection value", false, OsUtils.isWin32()); } finally { - OsUtils.setWin32(null); // force re-detection + OsUtils.setOS(null); // force re-detection } } diff --git a/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpShell.java b/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpShell.java index c215986..669e2aa 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpShell.java +++ b/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpShell.java @@ -27,6 +27,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; @@ -142,8 +143,7 @@ public class ScpShell extends AbstractFileSystemCommand { super.setFileSystemFactory(factory, session); } - protected void println( - String cmd, Object x, OutputStream out, Charset cs) { + protected void println(String cmd, Object x, OutputStream out, Charset cs) { try { String s = x.toString(); if (log.isDebugEnabled()) { @@ -603,8 +603,10 @@ public class ScpShell extends AbstractFileSystemCommand { protected void ls(String[] argv) throws Exception { // find options boolean optListAll = false; + boolean optDirAsPlain = false; boolean optLong = false; boolean optFullTime = false; + String path = null; for (int k = 1; k < argv.length; k++) { String argValue = argv[k]; if (GenericUtils.isEmpty(argValue)) { @@ -628,6 +630,9 @@ public class ScpShell extends AbstractFileSystemCommand { case 'a': optListAll = true; break; + case 'd': + optDirAsPlain = true; + break; case 'l': optLong = true; break; @@ -636,17 +641,19 @@ public class ScpShell extends AbstractFileSystemCommand { return; } } + } else if (path == null) { + path = argValue; } else { signalError(argv[0], "unsupported option: " + argValue); return; } } - doLs(argv, optListAll, optLong, optFullTime); + doLs(argv[0], path, optListAll, optLong, optFullTime); } protected void doLs( - String argv[], boolean optListAll, boolean optLong, boolean optFullTime) + String cmd, String path, boolean optListAll, boolean optLong, boolean optFullTime) throws Exception { // list current directory content Predicate<Path> filter = p -> { @@ -656,15 +663,26 @@ public class ScpShell extends AbstractFileSystemCommand { }; // TODO make sure not listing above user's home directory - String[] synth = currentDir.toString().equals("/") ? new String[] { "." } : new String[] { ".", ".." }; + Stream<Path> files = path != null + ? Stream.of(currentDir.resolve(path)) + : Stream.concat(Stream.of(".", "..").map(currentDir::resolve), Files.list(currentDir)); OutputStream stdout = getOutputStream(); - Stream.concat(Stream.of(synth).map(currentDir::resolve), Files.list(currentDir)) + OutputStream stderr = getErrorStream(); + variables.put(STATUS, 0); + files .filter(filter) .map(p -> new PathEntry(p, currentDir)) .sorted() - .map(p -> p.display(optLong, optFullTime)) - .forEach(str -> println(argv[0], str, stdout, nameEncodingCharset)); - variables.put(STATUS, 0); + .forEach(p -> { + try { + String str = p.display(optLong, optFullTime); + println(cmd, str, stdout, nameEncodingCharset); + } catch (NoSuchFileException e) { + println(cmd, cmd + ": " + p.path.toString() + ": no such file or directory", stderr, + nameEncodingCharset); + variables.put(STATUS, 1); + } + }); } protected static class PathEntry implements Comparable<PathEntry> { @@ -692,7 +710,11 @@ public class ScpShell extends AbstractFileSystemCommand { return Objects.toString(abs); } - public String display(boolean optLongDisplay, boolean optFullTime) { + public String display(boolean optLongDisplay, boolean optFullTime) throws NoSuchFileException { + if (attributes.isEmpty()) { + throw new NoSuchFileException(path.toString()); + } + String abbrev = shortDisplay(); if (!optLongDisplay) { return abbrev; @@ -765,7 +787,11 @@ public class ScpShell extends AbstractFileSystemCommand { // ignore } } - return path.toString(); + String str = path.toString(); + if (str.isEmpty()) { + return abs.getFileName().toString(); + } + return str; } protected static String toString(FileTime time, boolean optFullTime) { @@ -791,14 +817,16 @@ public class ScpShell extends AbstractFileSystemCommand { for (String view : views) { try { Map<String, Object> ta = Files.readAttributes( - path, view + ":*", IoUtils.EMPTY_LINK_OPTIONS); + path, view + ":*", IoUtils.getLinkOptions(false)); ta.forEach(attrs::putIfAbsent); } catch (IOException e) { // Ignore } } - attrs.computeIfAbsent("isExecutable", s -> Files.isExecutable(path)); - attrs.computeIfAbsent("permissions", s -> IoUtils.getPermissionsFromFile(path.toFile())); + if (!attrs.isEmpty()) { + attrs.computeIfAbsent("isExecutable", s -> Files.isExecutable(path)); + attrs.computeIfAbsent("permissions", s -> IoUtils.getPermissionsFromFile(path.toFile())); + } return attrs; } }