Track progress of dd command called using open3 in ruby

193 views Asked by At

I am trying to monitor the progress of copying a raspberry-pi OS image to a microSD card. This is similar to Kill a process called using open3 in ruby, except I'm not killing the process, I'm sending it a command for it to issue a progress message.

  
    rpath = device_path.gsub(/disk/, "rdisk")
    puts "\n\nCopying image to #{rpath}"

    if false
      stdout_err, status = Open3.capture2e( "sudo", "dd", "bs=1m", "if=#{source_path}", "of=#{rpath}" )
      puts stdout_err
    else
      cmd = "sudo dd bs=1m if=#{source_path} of=#{rpath}"
      Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
        Thread.new do
          stdout_err.each {|l| puts l}
        end

        Thread.new do
          while true
            sleep 5
            if true
              Process.kill("INFO", wait_thr.pid)      #Tried INFO, SIGINFO, USR1, SIGUSR1
                                                      # all give:  `kill': Operation not permitted (Errno::EPERM)
            else
              stdin.puts  20.chr      #Should send ^T -- has no effect, nothing to terminal during flash
            end
          end
        end

        wait_thr.value
      end

The first section (after 'if false') flashes the image using Open3.capture2e. This works, but of course issues no progress information.

The section after the 'else' flashes the image using Open3.popen2e. It also attempts to display progress by either issuing 'Process.kill("INFO", wait_thr.pid)', or by sending ^T (20.chr) to the stdin stream every 5 seconds.

The Process.kill line generates an "Operation not permitted" error. The stdin.puts line has no effect at all.

One other thing... While the popen2e process is flashing, hitting ctrl-T on the keyboard DOES generate a progress response. I just can't get it to do it programmatically.

Any help is appreciated!

1

There are 1 answers

2
loremdipso On

Newer versions of dd have an optional progress bar, as seen here. Even so I think you'll want to rethink how you execute that shell command so that it thinks it's attached to a terminal. Easiest thing to do is fork/exec, like:

cmd = "sudo dd bs=1m if=#{source_path} of=#{rpath} status=progress"
fork do
    exec(cmd) # this replaces the forked process with the cmd, giving it direct access to your terminal
end

Process.wait() # waits for the child process to exit

If that's not an option you may want to look into other ways of getting unbuffered output, including just writing a bash script instead of a ruby one.