From 86d81296451a3e58b328898d1bf554c10f265a22 Mon Sep 17 00:00:00 2001 From: Thomas Fitzsimmons Date: Wed, 24 Jun 2026 18:19:42 -0400 Subject: [PATCH] 8366261: Provide utility methods for sun.security.util.Password Backport-of: 91417890a24450d7cb23d188df4f631304d41ed6 --- .../share/classes/java/io/Console.java | 76 ++++++++++++++++++- .../jdk/internal/misc/JavaIOAccess.java | 5 +- .../unix/native/libjava/Console_md.c | 19 ++++- .../windows/native/libjava/Console_md.c | 38 ++++++---- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index d427f7208b2..da7f0ff4b34 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -26,7 +26,9 @@ package java.io; import java.util.*; +import java.lang.annotation.Native; import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicReference; import jdk.internal.misc.JavaIOAccess; import jdk.internal.misc.SharedSecrets; import sun.nio.cs.StreamDecoder; @@ -308,6 +310,51 @@ public String readLine() { * or {@code null} if an end of stream has been reached. */ public char[] readPassword(String fmt, Object ... args) { + return readPassword0(false, fmt, args); + } + + // These two methods are intended for sun.security.util.Password, so tools like keytool can + // use Console even when standard output is redirected. The Password class should first + // check if `System.console()` returns a Console instance and use it if available. Otherwise, + // it should call this method to obtain a Console. This ensures only one Console + // instance exists in the Java runtime. + private static final AtomicReference> INSTANCE = new AtomicReference<>(); + private static Optional passwordConsole() { + Optional result = INSTANCE.get(); + if (result != null) { + return result; + } + + synchronized (Console.class) { + result = INSTANCE.get(); + if (result != null) { + return result; + } + + // If there's already a proper console, throw an exception + if (System.console() != null) { + throw new IllegalStateException("Can't create a dedicated password " + + "console since a real console already exists"); + } + + // If stdin is NOT redirected, return an Optional containing a Console + // instance, otherwise an empty Optional. + result = isStdinTty() ? + Optional.of( + new Console()) : + Optional.empty(); + + INSTANCE.set(result); + return result; + } + } + + // Dedicated entry for sun.security.util.Password when stdout is redirected. + private char[] readPasswordNoNewLine() { + return readPassword0(true, ""); + } + + private char[] readPassword0(boolean noNewLine, String fmt, Object ... args) { char[] passwd = null; synchronized (writeLock) { synchronized(readLock) { @@ -347,7 +394,9 @@ public char[] readPassword(String fmt, Object ... args) { throw ioe; } } - pw.println(); + if (!noNewLine) { + pw.println(); + } } } return passwd; @@ -411,6 +460,12 @@ public void flush() { private boolean restoreEcho; private boolean shutdownHookInstalled; private static native String encoding(); + @Native private static final int TTY_STDIN_MASK = 0x00000001; + @Native private static final int TTY_STDOUT_MASK = 0x00000002; + @Native private static final int TTY_STDERR_MASK = 0x00000004; + // ttyStatus() returns bit patterns above, a bit is set if the corresponding file + // descriptor is a character device + private static final int ttyStatus = ttyStatus(); /* * Sets the console echo status to {@code on} and returns the previous * console on/off status. @@ -575,7 +630,7 @@ public int read(char cbuf[], int offset, int length) static { SharedSecrets.setJavaIOAccess(new JavaIOAccess() { public Console console() { - if (istty()) { + if (isStdinTty() && isStdoutTty()) { if (cons == null) cons = new Console(); return cons; @@ -588,10 +643,15 @@ public Charset charset() { // cons already exists when this method is called return cons.cs; } + public Optional passwordConsole() { + return Console.passwordConsole(); + } + public char[] readPasswordNoNewLine(Console c) { + return c.readPasswordNoNewLine(); + } }); } private static Console cons; - private static native boolean istty(); private Console() { readLock = new Object(); writeLock = new Object(); @@ -615,4 +675,14 @@ private Console() { cs)); rcb = new char[1024]; } + private static boolean isStdinTty() { + return (ttyStatus & TTY_STDIN_MASK) != 0; + } + private static boolean isStdoutTty() { + return (ttyStatus & TTY_STDOUT_MASK) != 0; + } + private static boolean isStderrTty() { + return (ttyStatus & TTY_STDERR_MASK) != 0; + } + private static native int ttyStatus(); } diff --git a/src/java.base/share/classes/jdk/internal/misc/JavaIOAccess.java b/src/java.base/share/classes/jdk/internal/misc/JavaIOAccess.java index b4e8926f1f7..9e8480d79e5 100644 --- a/src/java.base/share/classes/jdk/internal/misc/JavaIOAccess.java +++ b/src/java.base/share/classes/jdk/internal/misc/JavaIOAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,8 +27,11 @@ import java.io.Console; import java.nio.charset.Charset; +import java.util.Optional; public interface JavaIOAccess { public Console console(); public Charset charset(); + public Optional passwordConsole(); + public char[] readPasswordNoNewLine(Console c); } diff --git a/src/java.base/unix/native/libjava/Console_md.c b/src/java.base/unix/native/libjava/Console_md.c index 829ceada934..e8b0a078930 100644 --- a/src/java.base/unix/native/libjava/Console_md.c +++ b/src/java.base/unix/native/libjava/Console_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,10 +32,21 @@ #include #include -JNIEXPORT jboolean JNICALL -Java_java_io_Console_istty(JNIEnv *env, jclass cls) +JNIEXPORT jint JNICALL +Java_java_io_Console_ttyStatus(JNIEnv *env, jclass cls) { - return isatty(fileno(stdin)) && isatty(fileno(stdout)); + jint ret = 0; + + if (isatty(fileno(stdin))) { + ret |= java_io_Console_TTY_STDIN_MASK; + } + if (isatty(fileno(stdout))) { + ret |= java_io_Console_TTY_STDOUT_MASK; + } + if (isatty(fileno(stderr))) { + ret |= java_io_Console_TTY_STDERR_MASK; + } + return ret; } JNIEXPORT jstring JNICALL diff --git a/src/java.base/windows/native/libjava/Console_md.c b/src/java.base/windows/native/libjava/Console_md.c index 173b2ffeb4a..4825afd3147 100644 --- a/src/java.base/windows/native/libjava/Console_md.c +++ b/src/java.base/windows/native/libjava/Console_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,23 +31,30 @@ #include #include -static HANDLE hStdOut = INVALID_HANDLE_VALUE; -static HANDLE hStdIn = INVALID_HANDLE_VALUE; -JNIEXPORT jboolean JNICALL -Java_java_io_Console_istty(JNIEnv *env, jclass cls) +JNIEXPORT jint JNICALL +Java_java_io_Console_ttyStatus(JNIEnv *env, jclass cls) { - if (hStdIn == INVALID_HANDLE_VALUE && - (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) { - return JNI_FALSE; + jint ret = 0; + HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE); + + if (hStdIn != INVALID_HANDLE_VALUE && + GetFileType(hStdIn) == FILE_TYPE_CHAR) { + ret |= java_io_Console_TTY_STDIN_MASK; + } + + if (hStdOut != INVALID_HANDLE_VALUE && + GetFileType(hStdOut) == FILE_TYPE_CHAR) { + ret |= java_io_Console_TTY_STDOUT_MASK; } - if (hStdOut == INVALID_HANDLE_VALUE && - (hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE) { - return JNI_FALSE; + + if (hStdErr != INVALID_HANDLE_VALUE && + GetFileType(hStdErr) == FILE_TYPE_CHAR) { + ret |= java_io_Console_TTY_STDERR_MASK; } - if (GetFileType(hStdIn) != FILE_TYPE_CHAR || - GetFileType(hStdOut) != FILE_TYPE_CHAR) - return JNI_FALSE; - return JNI_TRUE; + + return ret; } JNIEXPORT jstring JNICALL @@ -67,6 +74,7 @@ Java_java_io_Console_echo(JNIEnv *env, jclass cls, jboolean on) { DWORD fdwMode; jboolean old; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); if (! GetConsoleMode(hStdIn, &fdwMode)) { JNU_ThrowIOExceptionWithLastError(env, "GetConsoleMode failed"); return !on;