Maxence Henneron
Maxence Henneron

Reputation: 495

Double fork and stdin

I wrote this code to run my process in a daemon. The goal is to make this process running even if I close its parent. Now, i would like to be able to write something in its stdin. What should I do ? Here's the code.

def daemonize(cmd, options = {})
  rd, wr = IO.pipe
  p1 = Process.fork {
    Process.setsid
    p2 = Process.fork {
      $0 =  cmd #Name of the command
      pidfile = File.new(options[:pid_file], 'w')
      pidfile.chmod( 0644 )
      pidfile.puts "#{Process.pid}"
      pidfile.close
      Dir.chdir(ENV["PWD"] = options[:working_dir].to_s) if options[:working_dir]
      File.umask 0000
      STDIN.reopen '/dev/null'
      STDOUT.reopen '/dev/null', 'a'
      STDERR.reopen STDOUT
      Signal.trap("USR1") do
        Console.show 'I just received a USR1', 'warning'
      end
      ::Kernel.exec(*Shellwords.shellwords(cmd)) #Executing the command in the parent process
      exit
    }
    raise 'Fork failed!' if p2 == -1
    Process.detach(p2) # divorce p2 from parent process (p1)
    rd.close
    wr.write p2
    wr.close
    exit
  }
  raise 'Fork failed!' if p1 == -1
  Process.detach(p1) # divorce p1 from parent process (shell)
  wr.close
  daemon_id = rd.read.to_i
  rd.close
  daemon_id
end

Is there a way to reopen stdin in something like a pipe instead of /dev/null in which I would be able to write ?

Upvotes: 4

Views: 452

Answers (1)

Jon Cairns
Jon Cairns

Reputation: 11951

How about a fifo? In linux, you can use the mkfifo command:

$ mkfifo /tmp/mypipe

Then you can reopen STDIN on that pipe:

STDIN.reopen '/tmp/mypipe'
# Do read-y things

Anything else can write to that pipe:

$ echo "roflcopter" > /tmp/mypipe

allowing that data to be read by the ruby process.

(Update) Caveat with blocking

Since fifos block until there's a read and write (e.g. a read is blocked unless there's a write, and vice-versa), it's best handled with multiple threads. One thread should do the reading, passing the data to a queue, and another should handle that input. Here's an example of that situation:

require 'thread'

input = Queue.new
threads = []

# Read from the fifo and add to an input queue (glorified array)
threads << Thread.new(input) do |ip|
  STDIN.reopen 'mypipe'
  loop do
    if line = STDIN.gets
      puts "Read: #{line}"
      ip.push line
    end
  end
end

# Handle the input passed by the reader thread
threads << Thread.new(input) do |ip|
  loop do
    puts "Ouput: #{ip.pop}"
  end
end

threads.map(&:join)

Upvotes: 2

Related Questions