Module: Cognizant::Process::Execution

Included in:
Cognizant::Process
Defined in:
lib/cognizant/process/execution.rb

Defined Under Namespace

Classes: ExecutionResult

Instance Method Summary collapse

Instance Method Details

#execute(command, options = {}) ⇒ Object


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/cognizant/process/execution.rb', line 14

def execute(command, options = {})
  options[:groups] ||= []
  options[:env]    ||= {}

  pid, pid_w = IO.pipe

  unless options[:daemonize]
    # Stdout and stderr file descriptors.
    out_r, out_w = IO.pipe
    err_r, err_w = IO.pipe
  end

  # Run the following in a fork so that the privilege changes do not affect the parent process.
  fork_pid = ::Process.fork do
    # Set the user and groups for the process in context.
    drop_privileges(options)

    if options[:daemonize]
      # Create a new session to detach from the controlling terminal.
      unless ::Process.setsid
        Log[self].error "Cannot detach #{options[:name]} from controlling terminal"
      end

      # TODO: Set pgroup: true so that the spawned process is the group leader, and it's death would kill all children as well.

      # Prevent inheriting signals from parent process.
      setup_execution_traps

      # Give the process a name.
      $0 = options[:name] if options[:name]

      # Collect logs only when daemonizing.
      stdout = [options[:logfile] || "/dev/null", "a"]
      stderr = [options[:errfile] || options[:logfile] || "/dev/null", "a"]
    else
      stdout = out_w
      stderr = err_w
    end

    # TODO: Run popen as spawned process before privileges are dropped for increased abilities?
    stdin_data = options[:input] if options[:input]
    stdin_data = IO.popen(options[:input_command]).read if options[:input_command]

    if stdin_data
      stdin, stdin_w = IO.pipe
      stdin_w.write stdin_data
      stdin_w.close # stdin is closed by ::Process.spawn.
    elsif options[:input_file] and File.exists?(options[:input_file])
      stdin = options[:input_file]
    else
      stdin = "/dev/null"
    end

    # Merge spawn options.
    spawn_options = construct_spawn_options(options, {
      :in  => stdin,
      :out => stdout,
      :err => stderr
    })

    # Spawn a process to execute the command.
    process_pid = ::Process.spawn(options[:env].deep_stringify_keys!, command, spawn_options)
    # Log[self].debug "process_pid: #{process_pid} (#{command})"
    pid_w.write(process_pid)

    if options[:daemonize]
      # We do not care about actual status or output when daemonizing.
      exit(0)
    else
      # TODO: Timeout here, in case the process doesn't daemonize itself.

      # Wait (blocking) until the command has finished running.
      status = ::Process.waitpid2(process_pid)[1]

      # Pass on the exit status to the parent.
      exit(status.exitstatus || 0) # TODO: This 0 or something else should be controlled by timeout handler.
    end
  end

  # Close the pid file descriptor.
  pid_w.close

  if options[:daemonize]
    # Detach (non blocking) the process executing the command and move on.
    ::Process.detach(fork_pid)

    return ExecutionResult.new(
      pid.read.to_i,
      nil,
      nil,
      0,
      true
    )
  else
    # Wait until the fork has finished running and accept the exit status.
    status = ::Process.waitpid2(fork_pid)[1]

    # Timeout and try (detach + pid_running?)?

    # Close the file descriptors.
    out_w.close
    err_w.close

    # Collect and return stdout, stderr and exitcode.
    return ExecutionResult.new(
      nil,
      out_r.read,
      err_r.read,
      status.exitstatus,
      status.exitstatus ? status.exitstatus.zero? : false # TODO: What rare case would not have status.existatus?
    )
  end
end