kbredemeier

kbredemeier

Streaming tar files

Hello,

I am trying to download a tarball, extract and decompress a file from it and write it somewhere on the disk in one go. Basically wget -qO- http://my_server/archive.tar | tar -xf - data/some.tar.gz -O | tar -xzf -.

Since erl_tar does not look like it supports streams I decided using a port with tar to do the extraction but I am failing doing so.

Here is the source of my GenServer that opens the port and is supposed to extract the inner gzip compressed file:

defmodule UnTar do
  use GenServer
  require Logger

  defstruct port: nil, collector_fun: nil, collector_acc: nil

  def start_link(opts \\ []) do
    {server_opts, otp_opts} = Keyword.split(opts, [:into])
    GenServer.start_link(__MODULE__, server_opts, opts)
  end

  @impl true
  def init(opts) do
    into = Keyword.fetch!(opts, :into)

    tar = tar_exe()

    tar_args = ["-xf", "-", "data/some.tar.gz", "-O"]

    port_args = [
      {:args, tar_args},
      :use_stdio,
      :binary,
      :exit_status
    ]

    port = Port.open({:spawn_executable, tar}, port_args)
    {collector_acc, collector_fun} = Collectable.into(into)

    {:ok,
     %__MODULE__{
       port: port,
       collector_fun: collector_fun,
       collector_acc: collector_acc
     }}
  end

  def send_chunk(pid, chunk) do
    GenServer.call(pid, {:send_chunk, chunk})
  end

  @impl true
  def handle_call({:send_chunk, chunk}, _from, state) do
    Port.command(state.port, chunk)
    {:reply, :ok, state}
  end

  @impl true
  def handle_info(
        {_port, {:data, data}},
        %{collector_fun: fun, collector_acc: acc} = state
      ) do
    Logger.info("receiving chunk form port")
    new_acc = apply(fun, [acc, {:cont, data}])
    {:noreply, %{state | collector_acc: new_acc}}
  end

  def handle_info({_port, {:exit_status, 0}}, state) do
    Logger.info("exiting normal")
    {:stop, :normal, %{state | port: nil}}
  end

  def handle_info({_port, {:exit_status, status}}, state) do
    Logger.info("exiting with #{status}")
    {:stop, {:exit, status}, %{state | port: nil}}
  end

  defp tar_exe do
    System.find_executable("tar") || raise("Could not find `tar` executable.")
  end
end

This is how I use the server:

source_stream = File.stream!("path/to/source_archive.tar", [:read, :binary], 512)
target_stream = File.stream!("path/to/target", [:write, :binray])

{:ok, pid} = UnTar.start_link(into: target_stream)

source
|> Stream.map(fn chunk ->
  UnTar.send_chunk(pid, chunk)
end)
|> Stream.run()

At the end tar prints prints out:

/usr/bin/tar: data/some.tar.gz: Cannot write: Broken pipe                                                                                                                                                                          
/usr/bin/tar: Exiting with failure status due to previous errors  

The resulting file is corrupted and tar does not send any exit code to my server.
Any idea what I am doing wrong?

Edit:
Forgot to add the bytes_or_line arg to the source stream. I wonder if this might have something to do with tar not being able to terminate the end of the file. tar uses a block size of 512 bytes and if I don’t provide the block size tar is additionally printing /usr/bin/tar: A lone zero block at 51035

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement