質問

This is an error that I encountered in a large ruby script. I've managed to boil it down to what seems like the essence of the problem. In discussing this problem, the script was run by someone on OS/X without encountering the same problem, so rather then a ruby problem it would seem to be a linux problem.

#!/usr/bin/env ruby

require "io/console"


def read_char
  state=`stty -g`
  `stty raw -echo`
  input = $stdin.getc.chr
  input << $stdin.read_nonblock(3) rescue nil
  return input
 ensure 
  `stty #{state}`
  # Have also tried `stty -raw echo` 
end

system("strace -o strace.log ./trace the start")
puts "press a cursor key"
c=read_char
system("strace -o strace2.log ./trace regular key")
puts "press a regular key"
c=read_char
system("strace -o strace3.log ./trace cursor key")
puts "done press return to exit"

Things to mention about this code for non Rubyists, << appends to an array [1].

Backticks act pretty mush the same as in sh. At the moment, I do not know how Ruby specifically implements read_nonblock. I am digging into it. Will mention it if the question is still unanswered.

Later I will post the C++ source to the "trace" function but it is easy to understand without the source. Read the command-line and write to a predetermined file. Then ask for input. After reading some input from STDIN save it to the same file and exit. It is essentially a debugging function which temporarily replaces some more complicated functions.

The problem

The third system call behaves inappropriately, sort of like it was receiving it's input from /dev/zero.

The log entries for the reads from STDIN look like:

strace.log:read(0, "1\n", 1024)                = 2
strace2.log:read(0, "2\n", 1024)               = 2
strace3.log:read(0, 0x7fb388ba0000, 1024)      = -1 EAGAIN (Resource temporarily unavailable)

So why is the third trace reading memory mapped IO?

The entry for the command which allocated the memory is:

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb388ba0000

Why is the mmap command taking an fd of -1 as an argument? [2]

Additional Comment. Based on what I've described so far, I pretty much have to conclude that when the EAGAIN error happens, something is not properly clean up, or the stdin state is somehow messed up, and now stdin is clogged. THat this then gets inherited by the system command. Thus the title question: how do you reset stdin, so that it behaves as it did so initially.

[1] Yeah as a C++ programmer I understand how this can be annoying.

[2] I'm not that familar with Unix memory mapped IO never having written anything using it myself. Windows memory mapped IO I did use a long time ago.

役に立ちましたか?

解決

mmap() takes -1 as file descriptor as the flag MAP_ANONYMOUS is given.

MAP_ANONYMOUS gives a zero initialized buffer. No file is read.

Thus it gives a r/w region of 4096 zero initialized bytes at address 0x7fb388ba0000.


Update. OK, had a further look at it.

Most likely stdin is left with O_NONBLOCK after $stdin.read_nonblock(3).

Not sure how to unset or prevent this with conventional ruby I/O functions, you can however fix it by using fcntl. So, in Ruby something like this:

require 'fcntl'

def read_char
    ...
ensure
    flags = $stdin.fcntl(Fcntl::F_GETFL, 0)
    $stdin.fcntl(Fcntl::F_SETFL, (~Fcntl::O_NONBLOCK) & flags)
end

In C one could do something in direction of:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

...

char buf[32] = {0};
int fd, flags;

fd = fileno(stdin);
if (fd == -1) {
     perror("fileno");
     return 1;
}

flags = fcntl(fd, F_GETFL, 0);

if (flags & O_NONBLOCK) {
    flags &= (~O_NONBLOCK);
    fcntl(fd, F_SETFL, flags);
}

if(!(fgets(buf, 32, stdin)) {
    if (ferror (stdin))
        perror("fgets");
}

...
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top