Repository: borgbackup/borg

Issue:
borg extract --stdout fails with TypeError: '_Chunk' does not support the buffer interface
Easy one...

```
Traceback (most recent call last):
  File "/usr/lib64/python3.4/site-packages/borg/archiver.py", line 2127, in main
    exit_code = archiver.run(args)
  File "/usr/lib64/python3.4/site-packages/borg/archiver.py", line 2077, in run
    return args.func(args)
  File "/usr/lib64/python3.4/site-packages/borg/archiver.py", line 82, in wrapper
    return method(self, args, repository=repository, **kwargs)
  File "/usr/lib64/python3.4/site-packages/borg/archiver.py", line 92, in wrapper
    return method(self, args, repository=repository, manifest=manifest, key=key, archive=archive, **kwargs)
  File "/usr/lib64/python3.4/site-packages/borg/archiver.py", line 429, in do_extract
    original_path=orig_path)
  File "/usr/lib64/python3.4/site-packages/borg/archive.py", line 323, in extract_item
    sys.stdout.buffer.write(data)
TypeError: '_Chunk' does not support the buffer interface

Platform: Linux Cabochon 4.5.0-Vantage-dirty #1 SMP PREEMPT Tue Mar 29 13:02:33 EDT 2016 x86_64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
Linux: Gentoo Base System 2.2 
Borg: 1.0.3.dev210+ng088bccc  Python: CPython 3.4.3
PID: 30005  CWD: /home/cJ
```



Discussion:
Thanks for the report.

Looks like a missing test case right there :)

`sys.stdout.write(data.data)` works better.

But then, it should probably be a good idea to refuse to write folders to stdout, as the current behaviour (just concatenate) is useless. Write to cpio/tar format would be expected. Should probably have a `borg extract` that does either real extractions, or stdout cpio/tar, and some `borg cat-file` or an option to extract, to do something else.

What do you think?

To elaborate, something based on `tarfile` can work:

```
diff --git a/borg/archive.py b/home/cJ/mnt/Cabochon/usr/lib/python3.4/site-packages/borg/archive.py
index e630202..6464ef1 100644
--- a/borg/archive.py
+++ b/home/cJ/mnt/Cabochon/usr/lib/python3.4/site-packages/borg/archive.py
@@ -1,4 +1,5 @@
 from datetime import datetime, timezone
+import tarfile
 from getpass import getuser
 from itertools import groupby
 import errno
@@ -371,10 +372,18 @@ Number of files: {0.stats.nfiles}'''.format(
         """
         if dry_run or stdout:
             if b'chunks' in item:
+                if stdout:
+                    tarinfo = tarfile.TarInfo(name=item[b'path'])
+                    tarinfo.size = sum(x.size for x in item[b'chunks']) # TODO add more metadata...
+                    buf = tarinfo.tobuf()
+                    sys.stdout.buffer.write(buf)
+                    blocks, remainder = divmod(tarinfo.size, tarfile.BLOCKSIZE)
                 for data in self.pipeline.fetch_many([c.id for c in item[b'chunks']], is_preloaded=True):
                     if stdout:
-                        sys.stdout.buffer.write(data)
+                        sys.stdout.buffer.write(data.data)
                 if stdout:
+                    if remainder > 0:
+                        sys.stdout.buffer.write(tarfile.NUL * (tarfile.BLOCKSIZE - remainder))
                     sys.stdout.buffer.flush()
             return
```

But user interface is important.

we first need a simple bugfix for this, new feature ideas please go to a separate ticket first for discussion.


Resolving patch:
diff --git a/borg/archive.py b/borg/archive.py
--- a/borg/archive.py
+++ b/borg/archive.py
@@ -371,7 +371,7 @@ def extract_item(self, item, restore_attrs=True, dry_run=False, stdout=False, sp
         """
         if dry_run or stdout:
             if b'chunks' in item:
-                for data in self.pipeline.fetch_many([c.id for c in item[b'chunks']], is_preloaded=True):
+                for _, data in self.pipeline.fetch_many([c.id for c in item[b'chunks']], is_preloaded=True):
                     if stdout:
                         sys.stdout.buffer.write(data)
                 if stdout:


Test patch: