0

I'm sure this has been asked/answered, I just can't figure out search words to use to find the answer...

I've got a command line tool written in Java, running on UNIX, but could be Windows at some point. The program reads user input from System.in using BufferedReader.readline(). The user enters ^D (EOF) instead of ^U or whatever, System.in closes, and we are all very unhappy. I need to survive the EOF, either by disabling it, trapping and ignoring it, or being able to reopen System.in (which could be finding and opening the tty on UNIX, but how on Windows?). I'd like to do this transparently (unseen by the user), but since this will probably run from a script, I have UNIX (Windows/.bat) commands available).

Thanks in advance.

Blasanka
  • 21,001
  • 12
  • 102
  • 104
user3481644
  • 398
  • 2
  • 12

3 Answers3

0

You can trap signals, either by using internal API:

import sun.misc.Signal;
import sun.misc.SignalHandler;
public class NoStop {
    public static void main(final String[] args) {
        SignalHandler ignoreHandler = sig -> System.out.println("Ignoring signal " + sig);
        Signal.handle(new Signal("INT"), ignoreHandler);

or with jnr-posix :

import jnr.posix.POSIX;
import jnr.posix.POSIXFactory;
import jnr.posix.SignalHandler;
public class NoStop {
    public static void main(final String[] args) {
        SignalHandler ignoreHandler = sig -> System.out.println("Ignoring signal " + sig);
        POSIX posix = POSIXFactory.getPOSIX();
        posix.signal(Signal.SIGINT, ignoreHandler);

With that, typing ^C has no effect, and typing ^D results in null String read from System.in, which you can handle in your application logic.

The first one is using internal API, and the compiler complains:

Signal is internal proprietary API and may be removed in a future release

But it can come in handy to work around your issue, and sometimes it's OK. In your case it's trivial to compartmentalize to a minuscule scope, and find an alternative if the API actually disappears. If I had to, I'd pick the internal API in this particular case (bringing jnr-posix for that is overkill, but deserved a mention).

Tested on Mac & Linux, Java 8 & 9.

If you don't like the compiler warnings, invoke the methods using reflection(*).

Alternatively, you could launch your java program with a command line wrapper that would intercept signals for you (which you'd have to develop in C, this answer shows how). (I checked if rlwrap does that, I'm a bit disappointed it does not).

(*) (not saying it's better, just thought it could be a useful example)

private static void trapSignalsUsingReflection() {
    try {
        Class signalClass = Class.forName("sun.misc.Signal");
        Class signalHandlerInterface = Class.forName("sun.misc.SignalHandler");
        Object signal = signalClass.getDeclaredConstructor(String.class).newInstance("INT");
        Object handler = Proxy.newProxyInstance(
                NoStop.class.getClassLoader(),
                new Class[]{signalHandlerInterface},
                (proxy, method, args) -> {
                    System.out.println("Ignoring signal: " + args[0]);
                    return null;
                });
        Method handleMethod = signalClass.getMethod("handle", signalClass, signalHandlerInterface);
        handleMethod.invoke(null, signal, handler);
    }
    catch(ClassNotFoundException|IllegalAccessException|InstantiationException|NoSuchMethodException|InvocationTargetException e) {
        throw new RuntimeException("Oops, internal API has changed, quick," +
                "go to https://stackoverflow.com/a/34188676/8022004 & tell dimo414 he was wrong", e);
    }
}
qlown
  • 527
  • 3
  • 8
  • Trapping the signals is how I have done this using C, but given Java's platform independence, I never thought along that line. Unfortunately, as you state, the Signal API might be removed and will probably not be an acceptable solution. – user3481644 Jun 16 '17 at 12:24
-1

Please try the following code which I've tested on Ubuntu 16.04 LTS

public static void main(final String[] args) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String str = "";
    final StringBuilder message = new StringBuilder();
    boolean stopFlag = false;
    while (!stopFlag) {
        str = reader.readLine();
        if (str == null) {
            // need to reset stream and skip Exception
            System.in.reset();
            reader = new BufferedReader(new InputStreamReader(System.in));
            continue;
        }
        // some stop condition
        else if ("stop".equalsIgnoreCase(str)) {
            stopFlag = true;
            continue;
        }
        message.append(str);
    }
    System.out.println(message.toString());
}

Ctrl+D or Ctrl+C don't stop the app

  • Interesting, gave it a try. On Linux (Debian "Stretch"), and OS X El Capitan, `^C` stops the app, and `^D` throws `Exception in thread "main" java.io.IOException: Resetting to invalid mark` and then stops the app. – qlown Jun 10 '17 at 21:45
  • 1
    @qlown That's not surprising, because this answer here doesn't do anything to prevent closing the input stream. – Tom Jun 10 '17 at 22:24
  • @user3481644 sorry, it's my fail during testing : I didn't noticed that started app twice, during the first copy was running - second was working as expected. I've tried to reproduce that case and it always works till I re-compile the code. – Taras Shpulyar Jun 11 '17 at 00:12
-1

How do I reopen System.in after EOF?

System.in is not closed after it reads end of stream. You can keep reading from it (and keep getting end of stream). There is no problem here to solve.

Unless you closed it. Solution: don't.

or prevent EOF entirely?

You can't.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 2
    I suppose this is a matter of semantics, but reading from System.in and getting EOS each time is being "closed", being able to so just means the object is still around. The problem to be solved (if it can be) is to prevent a user from closing the stdin/stdout streams inadvertently, perhaps I should have my question more abstract. – user3481644 Jun 16 '17 at 12:28