Skip to content

Commit a12e9fc

Browse files
committed
8366261: Provide utility methods for sun.security.util.Password
Reviewed-by: smarks, weijun
1 parent cc6d34b commit a12e9fc

File tree

6 files changed

+106
-25
lines changed

6 files changed

+106
-25
lines changed

src/java.base/share/classes/java/io/Console.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package java.io;
2727

28+
import java.lang.annotation.Native;
2829
import java.util.*;
2930
import java.nio.charset.Charset;
3031
import jdk.internal.access.JavaIOAccess;
@@ -550,7 +551,12 @@ private static UnsupportedOperationException newUnsupportedOperationException()
550551
"Console class itself does not provide implementation");
551552
}
552553

553-
private static final boolean istty = istty();
554+
@Native static final int TTY_STDIN_MASK = 0x00000001;
555+
@Native static final int TTY_STDOUT_MASK = 0x00000002;
556+
@Native static final int TTY_STDERR_MASK = 0x00000004;
557+
// ttyStatus() returns bit patterns above, a bit is set if the corresponding file
558+
// descriptor is a character device
559+
private static final int ttyStatus = ttyStatus();
554560
private static final Charset STDIN_CHARSET =
555561
Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE);
556562
private static final Charset STDOUT_CHARSET =
@@ -562,6 +568,9 @@ private static UnsupportedOperationException newUnsupportedOperationException()
562568
public Console console() {
563569
return cons;
564570
}
571+
public boolean isStdinTty() {
572+
return Console.isStdinTty();
573+
}
565574
});
566575
}
567576

@@ -583,7 +592,7 @@ private static Console instantiateConsole() {
583592

584593
for (var jcp : ServiceLoader.load(ModuleLayer.boot(), JdkConsoleProvider.class)) {
585594
if (consModName.equals(jcp.getClass().getModule().getName())) {
586-
var jc = jcp.console(istty, STDIN_CHARSET, STDOUT_CHARSET);
595+
var jc = jcp.console(isStdinTty() && isStdoutTty(), STDIN_CHARSET, STDOUT_CHARSET);
587596
if (jc != null) {
588597
c = new ProxyingConsole(jc);
589598
}
@@ -594,12 +603,21 @@ private static Console instantiateConsole() {
594603
}
595604

596605
// If not found, default to built-in Console
597-
if (istty && c == null) {
606+
if (isStdinTty() && isStdoutTty() && c == null) {
598607
c = new ProxyingConsole(new JdkConsoleImpl(STDIN_CHARSET, STDOUT_CHARSET));
599608
}
600609

601610
return c;
602611
}
603612

604-
private static native boolean istty();
613+
private static boolean isStdinTty() {
614+
return (ttyStatus & TTY_STDIN_MASK) != 0;
615+
}
616+
private static boolean isStdoutTty() {
617+
return (ttyStatus & TTY_STDOUT_MASK) != 0;
618+
}
619+
private static boolean isStderrTty() {
620+
return (ttyStatus & TTY_STDERR_MASK) != 0;
621+
}
622+
private static native int ttyStatus();
605623
}

src/java.base/share/classes/jdk/internal/access/JavaIOAccess.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,4 +29,5 @@
2929

3030
public interface JavaIOAccess {
3131
Console console();
32+
boolean isStdinTty();
3233
}

src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@
3838
import java.util.Formatter;
3939
import java.util.Locale;
4040
import java.util.Objects;
41+
import java.util.Optional;
4142

4243
import jdk.internal.access.SharedSecrets;
44+
import jdk.internal.util.StaticProperty;
4345
import sun.nio.cs.StreamDecoder;
4446
import sun.nio.cs.StreamEncoder;
47+
import sun.nio.cs.UTF_8;
4548

4649
/**
4750
* JdkConsole implementation based on the platform's TTY.
@@ -103,6 +106,42 @@ public String readLine() {
103106

104107
@Override
105108
public char[] readPassword(Locale locale, String format, Object ... args) {
109+
return readPassword0(false, locale, format, args);
110+
}
111+
112+
// These two methods are intended for sun.security.util.Password, so tools like keytool can
113+
// use JdkConsoleImpl even when standard output is redirected. The Password class should first
114+
// check if `System.console()` returns a Console instance and use it if available. Otherwise,
115+
// it should call this method to obtain a JdkConsoleImpl. This ensures only one Console
116+
// instance exists in the Java runtime.
117+
private static final StableValue<Optional<JdkConsoleImpl>> INSTANCE = StableValue.of();
118+
public static Optional<JdkConsoleImpl> passwordConsole() {
119+
return INSTANCE.orElseSet(() -> {
120+
// If there's already a proper console, throw an exception
121+
if (System.console() != null) {
122+
throw new IllegalStateException("Can’t create a dedicated password " +
123+
"console since a real console already exists");
124+
}
125+
126+
// If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl
127+
// instance, otherwise an empty Optional.
128+
return SharedSecrets.getJavaIOAccess().isStdinTty() ?
129+
Optional.of(
130+
new JdkConsoleImpl(
131+
Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE),
132+
Charset.forName(StaticProperty.stdoutEncoding(), UTF_8.INSTANCE))) :
133+
Optional.empty();
134+
});
135+
}
136+
137+
// Dedicated entry for sun.security.util.Password when stdout is redirected.
138+
// This method strictly avoids producing any output by using noNewLine = true
139+
// and an empty format string.
140+
public char[] readPasswordNoNewLine() {
141+
return readPassword0(true, Locale.getDefault(Locale.Category.FORMAT), "");
142+
}
143+
144+
private char[] readPassword0(boolean noNewLine, Locale locale, String format, Object ... args) {
106145
char[] passwd = null;
107146
synchronized (writeLock) {
108147
synchronized(readLock) {
@@ -146,7 +185,9 @@ public char[] readPassword(Locale locale, String format, Object ... args) {
146185
throw ioe;
147186
}
148187
}
149-
pw.println();
188+
if (!noNewLine) {
189+
pw.println();
190+
}
150191
}
151192
}
152193
return passwd;

src/java.base/unix/native/libjava/Console_md.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,8 +31,19 @@
3131
#include <stdlib.h>
3232
#include <unistd.h>
3333

34-
JNIEXPORT jboolean JNICALL
35-
Java_java_io_Console_istty(JNIEnv *env, jclass cls)
34+
JNIEXPORT jint JNICALL
35+
Java_java_io_Console_ttyStatus(JNIEnv *env, jclass cls)
3636
{
37-
return isatty(fileno(stdin)) && isatty(fileno(stdout));
37+
jint ret = 0;
38+
39+
if (isatty(fileno(stdin))) {
40+
ret |= java_io_Console_TTY_STDIN_MASK;
41+
}
42+
if (isatty(fileno(stdout))) {
43+
ret |= java_io_Console_TTY_STDOUT_MASK;
44+
}
45+
if (isatty(fileno(stderr))) {
46+
ret |= java_io_Console_TTY_STDERR_MASK;
47+
}
48+
return ret;
3849
}

src/java.base/windows/native/libjava/Console_md.c

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,21 +31,28 @@
3131
#include <stdlib.h>
3232
#include <Wincon.h>
3333

34-
JNIEXPORT jboolean JNICALL
35-
Java_java_io_Console_istty(JNIEnv *env, jclass cls)
34+
JNIEXPORT jint JNICALL
35+
Java_java_io_Console_ttyStatus(JNIEnv *env, jclass cls)
3636
{
37+
jint ret = 0;
3738
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3839
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
40+
HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);
3941

40-
if (hStdIn == INVALID_HANDLE_VALUE ||
41-
hStdOut == INVALID_HANDLE_VALUE) {
42-
return JNI_FALSE;
42+
if (hStdIn != INVALID_HANDLE_VALUE &&
43+
GetFileType(hStdIn) == FILE_TYPE_CHAR) {
44+
ret |= java_io_Console_TTY_STDIN_MASK;
4345
}
4446

45-
if (GetFileType(hStdIn) != FILE_TYPE_CHAR ||
46-
GetFileType(hStdOut) != FILE_TYPE_CHAR) {
47-
return JNI_FALSE;
47+
if (hStdOut != INVALID_HANDLE_VALUE &&
48+
GetFileType(hStdOut) == FILE_TYPE_CHAR) {
49+
ret |= java_io_Console_TTY_STDOUT_MASK;
4850
}
4951

50-
return JNI_TRUE;
52+
if (hStdErr != INVALID_HANDLE_VALUE &&
53+
GetFileType(hStdErr) == FILE_TYPE_CHAR) {
54+
ret |= java_io_Console_TTY_STDERR_MASK;
55+
}
56+
57+
return ret;
5158
}

test/jdk/java/io/Console/ModuleSelectionTest.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
* questions.
2222
*/
2323

24-
/**
24+
/*
2525
* @test
26-
* @bug 8295803 8299689 8351435 8361613
26+
* @bug 8295803 8299689 8351435 8361613 8366261
2727
* @summary Tests System.console() returns correct Console (or null) from the expected
2828
* module.
2929
* @library /test/lib
@@ -92,9 +92,12 @@ public static void main(String... args) throws Throwable {
9292
var con = System.console();
9393
var pc = Class.forName("java.io.ProxyingConsole");
9494
var jdkc = Class.forName("jdk.internal.io.JdkConsole");
95-
var istty = (boolean)MethodHandles.privateLookupIn(Console.class, MethodHandles.lookup())
96-
.findStatic(Console.class, "istty", MethodType.methodType(boolean.class))
97-
.invoke();
95+
var lookup = MethodHandles.privateLookupIn(Console.class, MethodHandles.lookup());
96+
var istty = (boolean)lookup.findStatic(Console.class, "isStdinTty", MethodType.methodType(boolean.class))
97+
.invoke() &&
98+
(boolean)lookup.findStatic(Console.class, "isStdoutTty", MethodType.methodType(boolean.class))
99+
.invoke();
100+
98101
var impl = con != null ? MethodHandles.privateLookupIn(pc, MethodHandles.lookup())
99102
.findGetter(pc, "delegate", jdkc)
100103
.invoke(con) : null;

0 commit comments

Comments
 (0)