5

I'm trying to send a command to a minecraft server jar using /proc/{pid}/fd/0 but the server does not execute the command.

To replicate what I'm trying to do you can do this on a Debian based machine (possibly other Linux distributuions aswell).

What I use to test this:

  • Ubuntu 14.04
  • minecraft_server.jar (testing with 1.8)
  • OpenJDK Runtime Environment (installed with default-jre-headless)

First console:

$ java -jar minecraft_server.jar nogui

Response: [ ... server starts and waiting for input]

say hi

Response: [19:52:23] [Server thread/INFO]: [Server] hi

Second console:

Now when i switch to the second console, with the server still running in the first i write:

echo "say hi2" >> /proc/$(pidof java)/fd/0

Everything looks well until I switch back to the first console. I can see the text "say hi2" but the server hasn't recognized it. I can write another command in the first console again and it is as if the text inputted from the second console hasn't even existed.

Why is this? And more importantly, how do I use /proc/{pid}/fd/0 in a proper way to send commands to a java jar file?

I don't know if this is some kind of Java-thing that I'm not aware of, if I can use some flag or something when executing the server, or if it's the server jar itself that is the problem..

I'm aware that you can use screen, tail -f or some kind of server wrapper to accomplish this, but that's not what I'm after. I would like to send a command using this method, in some kind of way.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Joel Bergroth
  • 61
  • 1
  • 3
  • Perhaps you could accomplish your ultimate goal by doing something like piping an instance of socat into the stdin of the process when you start it, and then using that pathway to inject commands? – Chris Stratton Nov 05 '14 at 19:45

2 Answers2

5

It's not a Java thing. What you are trying is simply not doable.

Test it like this:

Console1:

 $ cat

This will basically echo anything you type on it as soon as you hit "return".

Console2: Find the process number of your cat command. Let's say it's NNN. Do:

$ echo Something > /proc/NNN/fd/0

Switch back to Console1. You'll see "Something" on the console output, but it's not echoed.

Why? Do

$ ls -l /proc/NNN/fd

And you may understand. All three descriptors, 0 for stdin, 1 for stdout and 2 for stderr are actually symbolic links, and all point to the same pseudoterminal slave (pts) device, which is the pts associated with your first terminal.

So basically, when you write to it, you actually write to the console output, not to its input. If you read from that file, you could steal some of the input that was supposed to go to the process in the first console (you are racing for this input). That's how a character device works.

The documentation for /proc says that:

/proc/[pid]/fd/

This is a subdirectory containing one entry for each file which the process has open, named by its file descriptor, and which is a symbolic link to the actual file. Thus, 0 is standard input, 1 standard output, 2 standard error, and so on.

So these are not the actual file descriptors opened by the process. They are just links to files (or in this case, character devices) with names that indicate which descriptor they are attached to in the given process. Their main duty is to tell you whether the process has redirected its file descriptors or has opened any new ones, and which resources they point to.

But if you want an alternative way of doing this, you can use a fifo - a named pipe.

Create a fifo by doing:

$ mkfifo myfifo

Run your java program:

$ java -jar minecraft_server.jar nogui < myfifo

Open another console. write

$ cat > myfifo

Now start typing things. Switch to the first console. You'll see your server executing your commands.

Mind your end-of-files, though. Several processes can write to the same fifo, but as soon as the last one closes it, your server will receive an EOF on its standard input.

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
0

It is possible to get around the fact that a named pipe is 'closed' when a process ends. You can to do this by keeping a file descriptor to the named pipe open in another process.

#! /bin/bash

# tac prints a file in reverse order (tac -> cat)
cmd="tac"

# create fifo called pipe
mkfifo pipe
# open pipe on the current process's file descriptor 3
exec 3<>pipe

bash -c "
    # child process inherits all file descriptors. So cmd must be run from a sub-
# process. This allows us to close fd so cmd does not inherit fd 3, but allow fd 3
# to remain open on parent process.
    exec 3>&-
    # start your cmd and redirect the named pipe to its stdin
    $cmd < pipe
" &

# write data to pipe
echo hello > pipe
echo world > pipe

# short wait before tidy up
sleep 0.1
# done writing data, so close fd 3 on parent (this) process
exec 3>&-
# tac now knows it will receive no more data so it prints its data and exits
# data is unbuffered, so all data is received immediately. Try `cat` instead to see.
# clean up pipe
rm pipe
Dunes
  • 37,291
  • 7
  • 81
  • 97