diff --git a/btrfs-snapshots-diff.py b/btrfs-snapshots-diff.py
old mode 100755
new mode 100644
index c42b29d8ba740ad1c2ee6273159c7f517f26d756..b20a1280c031f0f33285116bc3a8fb45baf6b784
--- a/btrfs-snapshots-diff.py
+++ b/btrfs-snapshots-diff.py
@@ -3,7 +3,7 @@
 
 # Displays differences in 2 Btrfs snapshots (from the same subvolume
 # obviously).
-# Uses btrfs send to compute the differences, decodes the stream and 
+# Uses btrfs send to compute the differences, decodes the stream and
 # displays the differences.
 # Can read data from parent and current snapshots, or from diff
 # file created with:
@@ -42,330 +42,409 @@ import re
 from collections import OrderedDict
 from os import unlink
 
-# From btrfs/send.h
-send_cmds = 'BTRFS_SEND_C_UNSPEC BTRFS_SEND_C_SUBVOL BTRFS_SEND_C_SNAPSHOT BTRFS_SEND_C_MKFILE BTRFS_SEND_C_MKDIR BTRFS_SEND_C_MKNOD BTRFS_SEND_C_MKFIFO BTRFS_SEND_C_MKSOCK BTRFS_SEND_C_SYMLINK BTRFS_SEND_C_RENAME BTRFS_SEND_C_LINK BTRFS_SEND_C_UNLINK BTRFS_SEND_C_RMDIR BTRFS_SEND_C_SET_XATTR BTRFS_SEND_C_REMOVE_XATTR BTRFS_SEND_C_WRITE BTRFS_SEND_C_CLONE BTRFS_SEND_C_TRUNCATE BTRFS_SEND_C_CHMOD BTRFS_SEND_C_CHOWN BTRFS_SEND_C_UTIMES BTRFS_SEND_C_END BTRFS_SEND_C_UPDATE_EXTENT'.split()
-
-send_attrs = 'BTRFS_SEND_A_UNSPEC BTRFS_SEND_A_UUID BTRFS_SEND_A_CTRANSID BTRFS_SEND_A_INO BTRFS_SEND_A_SIZE BTRFS_SEND_A_MODE BTRFS_SEND_A_UID BTRFS_SEND_A_GID BTRFS_SEND_A_RDEV BTRFS_SEND_A_CTIME BTRFS_SEND_A_MTIME BTRFS_SEND_A_ATIME BTRFS_SEND_A_OTIME BTRFS_SEND_A_XATTR_NAME BTRFS_SEND_A_XATTR_DATA BTRFS_SEND_A_PATH BTRFS_SEND_A_PATH_TO BTRFS_SEND_A_PATH_LINK BTRFS_SEND_A_FILE_OFFSET BTRFS_SEND_A_DATA BTRFS_SEND_A_CLONE_UUID BTRFS_SEND_A_CLONE_CTRANSID BTRFS_SEND_A_CLONE_PATH BTRFS_SEND_A_CLONE_OFFSET BTRFS_SEND_A_CLONE_LEN'.split()
-
-# From btrfs/ioctl.h:#define BTRFS_UUID_SIZE 16
-BTRFS_UUID_SIZE = 16
-
-#if __name__ == "__main__":
-# Parse command line arguments
-parser = argparse.ArgumentParser(description="Display differences between 2 Btrfs snapshots")
-parser.add_argument('-p', '--parent',
-                    help="parent snapshot (must exists and be readonly)")
-parser.add_argument('-c', '--child',
-                    help="child snapshot (will be created if it does not exist)")
-parser.add_argument('-f', '--file',
-                    help="diff file")
-parser.add_argument('-v', '--verbose', action="count", default=0,
-                    help="increase verbosity")
-args = parser.parse_args()
-
-if args.parent is not None:
-   if args.child is not None:
-      cmd = ['btrfs', 'send', '-p', args.parent, args.child, '--no-data',
-         '-f', '/tmp/snaps-diff']
-      try:
-         subprocess.check_call(cmd)
-
-      except:
-         printerr('Error: %s\nexecuting "%s"\n' % (exc_info()[0], ' '.join(cmd)))
-         exit(1)
-      stream_file = '/tmp/snaps-diff'
-   else:
-      printerr('Error: parent needs child!\n')
-      parser.print_help() 
-      exit(1)
-
-elif args.file is None:
-   parser.print_help() 
-   exit(1)
-
-else:
-   stream_file = args.file
-
-# Read send stream
-try:
-   f_stream = open(stream_file)
-   stream = f_stream.read()
-   f_stream.close()
-   unlink(stream_file)
-
-except:
-   printerr('Error reading stream\n')
-   exit(1)
-
-# Temporary files / dirs / links... created by btrfs send: they are later
-# renamed to definitive files / dirs / links...
-re_tmp = re.compile(r'o\d+-\d+-0$')
-
-# Global header
-l_head = 17
-magic, null, version = unpack('<12scI', stream[0:l_head])
-if magic != 'btrfs-stream':
-   printerr('Not a Btrfs stream!\n')
-   exit(1)
-print 'Data is a Brtfs stream version %d' %  version
-
-# Headers length
-l_head = 10
-l_tlv = 4
-
-def tlv_get(attr_type, index):
-   attr, l_attr = unpack('<HH', stream[index:index+l_tlv])
-   if send_attrs[attr] != attr_type:
-      raise ValueError('Unexpected attribute %s' % send_attrs[attr])
-   ret, = unpack('<H', stream[index+l_tlv:index+l_tlv+2]) # XXX l_attr])
-   return index + l_tlv + l_attr, ret
-
-def tlv_get_string(attr_type, index):
-   attr, l_attr = unpack('<HH', stream[index:index+l_tlv])
-   if send_attrs[attr] != attr_type:
-      raise ValueError('Unexpected attribute %s' % send_attrs[attr])
-   ret, = unpack('<%ds' % l_attr, stream[index+l_tlv:index+l_tlv+l_attr])
-   return index + l_tlv + l_attr, ret
-
-def tlv_get_u64(attr_type, index):
-   attr, l_attr = unpack('<HH', stream[index:index+l_tlv])
-   if send_attrs[attr] != attr_type:
-      raise ValueError('Unexpected attribute %s' % send_attrs[attr])
-   ret, = unpack('<Q', stream[index+l_tlv:index+l_tlv+l_attr])
-   return index + l_tlv + l_attr, ret
-
-def tlv_get_uuid(attr_type, index):
-   attr, l_attr = unpack('<HH', stream[index:index+l_tlv])
-   if send_attrs[attr] != attr_type:
-      raise ValueError('Unexpected attribute %s' % send_attrs[attr])
-   ret = unpack('<' + 'B' * BTRFS_UUID_SIZE, stream[index + l_tlv:index + l_tlv + l_attr])
-   return index + l_tlv + l_attr, ''.join(['%02x' % x for x in ret])
-
-def tlv_get_timespec(attr_type, index):
-   attr, l_attr = unpack('<HH', stream[index:index+l_tlv])
-   if send_attrs[attr] != attr_type:
-      raise ValueError('Unexpected attribute %s' % send_attrs[attr])
-   s, ns = unpack('<QL', stream[index + l_tlv:index + l_tlv + l_attr])
-   return index + l_tlv + l_attr, float(s) + ns * 1e-9
-
-# Decode commands + attributes
-idx = 17
-count = 0
-# List of commands
-commands = []
-# modified[path] = [(command, cmd_ref), ...]
-modified = OrderedDict()
-
-while True:
-
-   l_cmd, cmd, crc = unpack('<IHI', stream[idx:idx+l_head])
-   try:
-      command = send_cmds[cmd]
-   except:
-      raise ValueError('Unkown command %d' % cmd)
-
-   if command == 'BTRFS_SEND_C_RENAME':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, path_to = tlv_get_string('BTRFS_SEND_A_PATH_TO', idx2)
-      # Add bogus renamed_from command on destination to keep track of what happened
-      modified.setdefault(path_to, []).append(('renamed_from', count))
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), path, path_to))
-
-
-   elif command == 'BTRFS_SEND_C_SYMLINK':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, ino = tlv_get_string('BTRFS_SEND_A_INO', idx2) # XXX BTRFS_SEND_A_PATH_LINK in send-stream.c ???
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), ino))
-
-   elif command == 'BTRFS_SEND_C_LINK':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, path_link = tlv_get_string('BTRFS_SEND_A_PATH_LINK', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), path_link))
-
-   elif command == 'BTRFS_SEND_C_UTIMES':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, atime = tlv_get_timespec('BTRFS_SEND_A_ATIME', idx2)
-      idx2, mtime = tlv_get_timespec('BTRFS_SEND_A_MTIME', idx2)
-      idx2, ctime = tlv_get_timespec('BTRFS_SEND_A_CTIME', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), atime, mtime, ctime))
-
-   elif command in 'BTRFS_SEND_C_MKFILE BTRFS_SEND_C_MKDIR BTRFS_SEND_C_MKFIFO BTRFS_SEND_C_MKSOCK BTRFS_SEND_C_UNLINK BTRFS_SEND_C_RMDIR '.split():
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower()))
-
-   elif command == 'BTRFS_SEND_C_TRUNCATE':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, size = tlv_get_u64('BTRFS_SEND_A_SIZE', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), size))
-
-   elif command == 'BTRFS_SEND_C_SNAPSHOT':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, uuid = tlv_get_uuid('BTRFS_SEND_A_UUID', idx2)
-      idx2, ctransid = tlv_get_u64('BTRFS_SEND_A_CTRANSID', idx2)
-      idx2, clone_uuid = tlv_get_uuid('BTRFS_SEND_A_CLONE_UUID', idx2)
-      idx2, clone_ctransid = tlv_get_u64('BTRFS_SEND_A_CLONE_CTRANSID', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), uuid, ctransid, 
-         clone_uuid, clone_ctransid))
-
-   elif command == 'BTRFS_SEND_C_SUBVOL':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, uuid = tlv_get_uuid('BTRFS_SEND_A_UUID', idx2)
-      idx2, ctransid = tlv_get_u64('BTRFS_SEND_A_CTRANSID', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), uuid, ctransid))
-
-   elif command == 'BTRFS_SEND_C_MKNOD':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, mode = tlv_get_u64('BTRFS_SEND_A_MODE', idx2)
-      idx2, rdev = tlv_get_u64('BTRFS_SEND_A_RDEV', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), mode, rdev))
-
-   elif command == 'BTRFS_SEND_C_SET_XATTR':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, xattr_name = tlv_get_string('BTRFS_SEND_A_XATTR_NAME', idx2)
-      idx2, xattr_data = tlv_get('BTRFS_SEND_A_XATTR_DATA', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), xattr_name, xattr_data))
-
-   elif command == 'BTRFS_SEND_C_REMOVE_XATTR':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, xattr_name = tlv_get_string('BTRFS_SEND_A_XATTR_NAME', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), xattr_name))
-
-   elif command == 'BTRFS_SEND_C_WRITE':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, file_offset = tlv_get_u64('BTRFS_SEND_A_FILE_OFFSET', idx2)
-      idx2, xattr_data = tlv_get('BTRFS_SEND_A_XATTR_DATA', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), file_offset, xattr_data))
-
-   elif command == 'BTRFS_SEND_C_CLONE':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, file_offset = tlv_get_u64('BTRFS_SEND_A_FILE_OFFSET', idx2)
-      idx2, clone_len = tlv_get_u64('BTRFS_SEND_A_CLONE_LEN', idx2)
-      idx2, clone_uuid = tlv_get_uuid('BTRFS_SEND_A_CLONE_UUID', idx2)
-      idx2, clone_transid = tlv_get_u64('BTRFS_SEND_A_CLONE_TRANSID', idx2)
-      idx2, clone_path = tlv_get_string('BTRFS_SEND_A_CLONE_PATH', idx+l_head) # BTRFS_SEND_A_CLONE8PATH
-      idx2, clone_offset = tlv_get_u64('BTRFS_SEND_A_CLONE_OFFSET', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), file_offset, clone_len, 
-         clone_uuid, clone_transid, clone_path))
-
-   elif command == 'BTRFS_SEND_C_CHMOD':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, mode = tlv_get_u64('BTRFS_SEND_A_MODE', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), mode))
-
-   elif command == 'BTRFS_SEND_C_CHOWN':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, uid = tlv_get_u64('BTRFS_SEND_A_UID', idx2)
-      idx2, gid = tlv_get_u64('BTRFS_SEND_A_GID', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), uid, gid))
-
-   elif command == 'BTRFS_SEND_C_UPDATE_EXTENT':
-      idx2, path = tlv_get_string('BTRFS_SEND_A_PATH', idx+l_head)
-      idx2, file_offset = tlv_get_u64('BTRFS_SEND_A_FILE_OFFSET', idx2)
-      idx2, size = tlv_get_u64('BTRFS_SEND_A_SIZE', idx2)
-      modified.setdefault(path, []).append((command[13:].lower(), count))
-      commands.append((command[13:].lower(), file_offset, size))
-
-   elif command == 'BTRFS_SEND_C_END':
-      print 'END: %d commands done (%d = %d ?)' % (count+1, idx + l_head, len(stream))
-      break
-
-   elif command == 'BTRFS_SEND_C_UNSPEC':
-      pass
-
-   else:
-      # Shoud not happen
-      raise ValueError('Unexpected command %s' % command)
-
-   idx += l_head + l_cmd
-   count += 1
-
-
-for path, actions in modified.iteritems():
-
-#   if re_tmp.match(path):
-#      if not (actions[0][0] in ('mkfile', 'mkdir', 'symlink') and  actions[1][0] == 'rename') and \
-#         not (actions[0][0] == ('renamed_from') and  actions[1][0] == 'rmdir'):
-#         print path, '\n\t', actions, '=' * 20
-#      continue
-
-
-   if path == '':
-      path = '__sub_root__'
-   print '\n', path
-   prev_act = None
-
-   extents = []
-   print_actions = []
-   for a in actions:
-
-      cmd = commands[a[1]]
-
-      if prev_act == 'update_extent' and a[0] != 'update_extent':
-            print_actions.append('update extents %d -> %d' % (
-                  extents[0][0],
-                  extents[-1][0] + extents[-1][1]))
-
-      if a[0] == 'renamed_from':
-#         if re_tmp.match(cmd[1]):
-#            print '\trewritten'
-#         else:
-            print_actions.append('renamed from "%s"' % cmd[1])
-
-      elif a[0] == 'set_xattr':
-         print_actions.append('xattr %s %d' % cmd[1:])
-
-      elif a[0] == 'update_extent':
-         extents .append(cmd[1:])
-
-      elif a[0] == 'truncate':
-         print_actions.append('truncate %d' % cmd[1])
 
-      elif a[0] == 'chown':
-         print_actions.append('owner %d:%d' % cmd[1:])
-
-      elif a[0] == 'chmod':
-         print_actions.append('mode %o' % cmd[1])
+class BtrfsStream(object):
+
+    # From btrfs/send.h
+    send_cmds = 'BTRFS_SEND_C_UNSPEC BTRFS_SEND_C_SUBVOL BTRFS_SEND_C_SNAPSHOT BTRFS_SEND_C_MKFILE BTRFS_SEND_C_MKDIR BTRFS_SEND_C_MKNOD BTRFS_SEND_C_MKFIFO BTRFS_SEND_C_MKSOCK BTRFS_SEND_C_SYMLINK BTRFS_SEND_C_RENAME BTRFS_SEND_C_LINK BTRFS_SEND_C_UNLINK BTRFS_SEND_C_RMDIR BTRFS_SEND_C_SET_XATTR BTRFS_SEND_C_REMOVE_XATTR BTRFS_SEND_C_WRITE BTRFS_SEND_C_CLONE BTRFS_SEND_C_TRUNCATE BTRFS_SEND_C_CHMOD BTRFS_SEND_C_CHOWN BTRFS_SEND_C_UTIMES BTRFS_SEND_C_END BTRFS_SEND_C_UPDATE_EXTENT'.split()
+
+    send_attrs = 'BTRFS_SEND_A_UNSPEC BTRFS_SEND_A_UUID BTRFS_SEND_A_CTRANSID BTRFS_SEND_A_INO BTRFS_SEND_A_SIZE BTRFS_SEND_A_MODE BTRFS_SEND_A_UID BTRFS_SEND_A_GID BTRFS_SEND_A_RDEV BTRFS_SEND_A_CTIME BTRFS_SEND_A_MTIME BTRFS_SEND_A_ATIME BTRFS_SEND_A_OTIME BTRFS_SEND_A_XATTR_NAME BTRFS_SEND_A_XATTR_DATA BTRFS_SEND_A_PATH BTRFS_SEND_A_PATH_TO BTRFS_SEND_A_PATH_LINK BTRFS_SEND_A_FILE_OFFSET BTRFS_SEND_A_DATA BTRFS_SEND_A_CLONE_UUID BTRFS_SEND_A_CLONE_CTRANSID BTRFS_SEND_A_CLONE_PATH BTRFS_SEND_A_CLONE_OFFSET BTRFS_SEND_A_CLONE_LEN'.split()
+
+    # From btrfs/ioctl.h:#define BTRFS_UUID_SIZE 16
+    BTRFS_UUID_SIZE = 16
+
+    # Temporary files / dirs / links... created by btrfs send: they are later
+    # renamed to definitive files / dirs / links...
+    re_tmp = re.compile(r'o\d+-\d+-0$')
+
+    # Headers length
+    l_head = 10
+    l_tlv = 4
+
+    def __init__(self, stream_file, delete=False):
+
+        # Read send stream
+        try:
+            f_stream = open(stream_file)
+            self.stream = f_stream.read()
+            f_stream.close()
+
+        except:
+            printerr('Error reading stream\n')
+            exit(1)
+
+        if delete:
+            try:
+                unlink(stream_file)
+            except:
+                printerr('Warning: could not delete stream file "%s"\n' %
+                         stream_file)
+
+        magic, null, version = unpack('<12scI', self.stream[0:17])
+        if magic != 'btrfs-stream':
+            printerr('Not a Btrfs stream!\n')
+            exit(1)
+        print 'This is a Brtfs stream, version %d.' % version
+
+    def tlv_get(self, attr_type, index):
+        attr, l_attr = unpack('<HH', self.stream[index:index + self.l_tlv])
+        if self.send_attrs[attr] != attr_type:
+            raise ValueError('Unexpected attribute %s' % self.send_attrs[attr])
+        # XXX l_attr])
+        ret, = unpack('<H', self.stream[
+                      index + self.l_tlv:index + self.l_tlv + 2])
+        return index + self.l_tlv + l_attr, ret
+
+    def tlv_get_string(self, attr_type, index):
+        attr, l_attr = unpack('<HH', self.stream[index:index + self.l_tlv])
+        if self.send_attrs[attr] != attr_type:
+            raise ValueError('Unexpected attribute %s' % self.send_attrs[attr])
+        ret, = unpack('<%ds' % l_attr, self.stream[
+                      index + self.l_tlv:index + self.l_tlv + l_attr])
+        return index + self.l_tlv + l_attr, ret
+
+    def tlv_get_u64(self, attr_type, index):
+        attr, l_attr = unpack('<HH', self.stream[index:index + self.l_tlv])
+        if self.send_attrs[attr] != attr_type:
+            raise ValueError('Unexpected attribute %s' % self.send_attrs[attr])
+        ret, = unpack('<Q', self.stream[
+                      index + self.l_tlv:index + self.l_tlv + l_attr])
+        return index + self.l_tlv + l_attr, ret
+
+    def tlv_get_uuid(self, attr_type, index):
+        attr, l_attr = unpack('<HH', self.stream[index:index + self.l_tlv])
+        if self.send_attrs[attr] != attr_type:
+            raise ValueError('Unexpected attribute %s' % self.send_attrs[attr])
+        ret = unpack('<' + 'B' * self.BTRFS_UUID_SIZE,
+                     self.stream[index + self.l_tlv:index + self.l_tlv + l_attr])
+        return index + self.l_tlv + l_attr, ''.join(['%02x' % x for x in ret])
+
+    def tlv_get_timespec(self, attr_type, index):
+        attr, l_attr = unpack('<HH', self.stream[index:index + self.l_tlv])
+        if self.send_attrs[attr] != attr_type:
+            raise ValueError('Unexpected attribute %s' % self.send_attrs[attr])
+        s, ns = unpack('<QL', self.stream[
+                       index + self.l_tlv:index + self.l_tlv + l_attr])
+        return index + self.l_tlv + l_attr, float(s) + ns * 1e-9
+
+    def decode(self):
+        # Decode commands + attributes
+        idx = 17
+        count = 0
+        # List of commands
+        commands = []
+        # modified[path] = [(command, cmd_ref), ...]
+        modified = OrderedDict()
+
+        while True:
+
+            l_cmd, cmd, crc = unpack(
+                '<IHI', self.stream[idx:idx + self.l_head])
+            try:
+                command = self.send_cmds[cmd]
+            except:
+                raise ValueError('Unkown command %d' % cmd)
+
+            if command == 'BTRFS_SEND_C_RENAME':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, path_to = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH_TO', idx2)
+                # Add bogus renamed_from command on destination to keep track
+                # of what happened
+                modified.setdefault(path_to, []).append(
+                    ('renamed_from', count))
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), path, path_to))
+
+            elif command == 'BTRFS_SEND_C_SYMLINK':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                # XXX BTRFS_SEND_A_PATH_LINK in send-self.stream.c ???
+                idx2, ino = self.tlv_get_string('BTRFS_SEND_A_INO', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), ino))
+
+            elif command == 'BTRFS_SEND_C_LINK':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, path_link = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH_LINK', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), path_link))
+
+            elif command == 'BTRFS_SEND_C_UTIMES':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, atime = self.tlv_get_timespec('BTRFS_SEND_A_ATIME', idx2)
+                idx2, mtime = self.tlv_get_timespec('BTRFS_SEND_A_MTIME', idx2)
+                idx2, ctime = self.tlv_get_timespec('BTRFS_SEND_A_CTIME', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), atime, mtime, ctime))
+
+            elif command in 'BTRFS_SEND_C_MKFILE BTRFS_SEND_C_MKDIR BTRFS_SEND_C_MKFIFO BTRFS_SEND_C_MKSOCK BTRFS_SEND_C_UNLINK BTRFS_SEND_C_RMDIR '.split():
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower()))
+
+            elif command == 'BTRFS_SEND_C_TRUNCATE':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, size = self.tlv_get_u64('BTRFS_SEND_A_SIZE', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), size))
+
+            elif command == 'BTRFS_SEND_C_SNAPSHOT':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, uuid = self.tlv_get_uuid('BTRFS_SEND_A_UUID', idx2)
+                idx2, ctransid = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CTRANSID', idx2)
+                idx2, clone_uuid = self.tlv_get_uuid(
+                    'BTRFS_SEND_A_CLONE_UUID', idx2)
+                idx2, clone_ctransid = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CLONE_CTRANSID', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), uuid, ctransid,
+                                 clone_uuid, clone_ctransid))
+
+            elif command == 'BTRFS_SEND_C_SUBVOL':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, uuid = self.tlv_get_uuid('BTRFS_SEND_A_UUID', idx2)
+                idx2, ctransid = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CTRANSID', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), uuid, ctransid))
+
+            elif command == 'BTRFS_SEND_C_MKNOD':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, mode = self.tlv_get_u64('BTRFS_SEND_A_MODE', idx2)
+                idx2, rdev = self.tlv_get_u64('BTRFS_SEND_A_RDEV', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), mode, rdev))
+
+            elif command == 'BTRFS_SEND_C_SET_XATTR':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, xattr_name = self.tlv_get_string(
+                    'BTRFS_SEND_A_XATTR_NAME', idx2)
+                idx2, xattr_data = self.tlv_get(
+                    'BTRFS_SEND_A_XATTR_DATA', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), xattr_name, xattr_data))
+
+            elif command == 'BTRFS_SEND_C_REMOVE_XATTR':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, xattr_name = self.tlv_get_string(
+                    'BTRFS_SEND_A_XATTR_NAME', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), xattr_name))
+
+            elif command == 'BTRFS_SEND_C_WRITE':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, file_offset = self.tlv_get_u64(
+                    'BTRFS_SEND_A_FILE_OFFSET', idx2)
+                idx2, xattr_data = self.tlv_get(
+                    'BTRFS_SEND_A_DATA', idx2)  # BTRFS_SEND_A_XATTR_DATA
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append(
+                    (command[13:].lower(), file_offset, xattr_data))
+
+            elif command == 'BTRFS_SEND_C_CLONE':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, file_offset = self.tlv_get_u64(
+                    'BTRFS_SEND_A_FILE_OFFSET', idx2)
+                idx2, clone_len = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CLONE_LEN', idx2)
+                idx2, clone_uuid = self.tlv_get_uuid(
+                    'BTRFS_SEND_A_CLONE_UUID', idx2)
+                idx2, clone_transid = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CLONE_TRANSID', idx2)
+                idx2, clone_path = self.tlv_get_string(
+                    'BTRFS_SEND_A_CLONE_PATH', idx + self.l_head)  # BTRFS_SEND_A_CLONE8PATH
+                idx2, clone_offset = self.tlv_get_u64(
+                    'BTRFS_SEND_A_CLONE_OFFSET', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), file_offset, clone_len,
+                                 clone_uuid, clone_transid, clone_path))
+
+            elif command == 'BTRFS_SEND_C_CHMOD':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, mode = self.tlv_get_u64('BTRFS_SEND_A_MODE', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), mode))
+
+            elif command == 'BTRFS_SEND_C_CHOWN':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, uid = self.tlv_get_u64('BTRFS_SEND_A_UID', idx2)
+                idx2, gid = self.tlv_get_u64('BTRFS_SEND_A_GID', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), uid, gid))
+
+            elif command == 'BTRFS_SEND_C_UPDATE_EXTENT':
+                idx2, path = self.tlv_get_string(
+                    'BTRFS_SEND_A_PATH', idx + self.l_head)
+                idx2, file_offset = self.tlv_get_u64(
+                    'BTRFS_SEND_A_FILE_OFFSET', idx2)
+                idx2, size = self.tlv_get_u64('BTRFS_SEND_A_SIZE', idx2)
+                modified.setdefault(path, []).append(
+                    (command[13:].lower(), count))
+                commands.append((command[13:].lower(), file_offset, size))
+
+            elif command == 'BTRFS_SEND_C_END':
+                print 'END: %d commands done (%d = %d ?)' % (count + 1, idx + self.l_head, len(self.stream))
+                break
+
+            elif command == 'BTRFS_SEND_C_UNSPEC':
+                pass
+
+            else:
+                # Shoud not happen
+                raise ValueError('Unexpected command %s' % command)
+
+            idx += self.l_head + l_cmd
+            count += 1
+
+        return modified, commands
+
+
+if __name__ == "__main__":
+    # Parse command line arguments
+    parser = argparse.ArgumentParser(
+        description="Display differences between 2 Btrfs snapshots")
+    parser.add_argument('-p', '--parent',
+                        help="parent snapshot (must exists and be readonly)")
+    parser.add_argument('-c', '--child',
+                        help="child snapshot (will be created if it does not exist)")
+    parser.add_argument('-f', '--file',
+                        help="diff file")
+    parser.add_argument('-v', '--verbose', action="count", default=0,
+                        help="increase verbosity")
+    args = parser.parse_args()
+
+    if args.parent is not None:
+        if args.child is not None:
+            cmd = ['btrfs', 'send', '-p', args.parent, args.child, '--no-data',
+                   '-f', '/tmp/snaps-diff']
+            try:
+                subprocess.check_call(cmd)
+
+            except:
+                printerr('Error: %s\nexecuting "%s"\n' %
+                         (exc_info()[0], ' '.join(cmd)))
+                exit(1)
+            stream_file = '/tmp/snaps-diff'
+        else:
+            printerr('Error: parent needs child!\n')
+            parser.print_help()
+            exit(1)
+
+    elif args.file is None:
+        parser.print_help()
+        exit(1)
+
+    else:
+        stream_file = args.file
+
+    stream = BtrfsStream(stream_file)
+    modified, commands = stream.decode()
+
+    for path, actions in modified.iteritems():
+
+        #   if re_tmp.match(path):
+        #      if not (actions[0][0] in ('mkfile', 'mkdir', 'symlink') and  actions[1][0] == 'rename') and \
+        #         not (actions[0][0] == ('renamed_from') and  actions[1][0] == 'rmdir'):
+        #         print path, '\n\t', actions, '=' * 20
+        #      continue
+
+        if path == '':
+            path = '__sub_root__'
+        print '\n', path
+        prev_act = None
+
+        extents = []
+        print_actions = []
+        for a in actions:
+
+            cmd = commands[a[1]]
+
+            if prev_act == 'update_extent' and a[0] != 'update_extent':
+                print_actions.append('update extents %d -> %d' % (
+                    extents[0][0],
+                    extents[-1][0] + extents[-1][1]))
+
+            if a[0] == 'renamed_from':
+                #         if re_tmp.match(cmd[1]):
+                #            print '\trewritten'
+                #         else:
+                print_actions.append('renamed from "%s"' % cmd[1])
+
+            elif a[0] == 'set_xattr':
+                print_actions.append('xattr %s %d' % cmd[1:])
+
+            elif a[0] == 'update_extent':
+                extents .append(cmd[1:])
+
+            elif a[0] == 'truncate':
+                print_actions.append('truncate %d' % cmd[1])
+
+            elif a[0] == 'chown':
+                print_actions.append('owner %d:%d' % cmd[1:])
 
-      elif a[0] == 'link':
-         print_actions.append('link to "%s"' % cmd[1])
+            elif a[0] == 'chmod':
+                print_actions.append('mode %o' % cmd[1])
+
+            elif a[0] == 'link':
+                print_actions.append('link to "%s"' % cmd[1])
+
+            elif a[0] == 'symlink':
+                print_actions.append('symlink to "%s"' % cmd[1])
+
+            elif a[0] in ('unlink', 'mkfile', 'mkdir', 'mkfifo'):
+                print_actions.append('%s' % a[0])
+
+            elif a[0] == 'rename':
+                print_actions.append('rename to "%s"' % cmd[2])
+
+            elif a[0] == 'utimes':
+                print_actions.append('times a=%s m=%s c=%s' % (
+                    time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[1])),
+                    time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[2])),
+                    time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[3]))
+                ))
+
+            elif a[0] == 'snapshot':
+                print_actions.append(
+                    'snapshot: uuid=%s, ctrasid=%d, clone_uuid=%s, clone_ctransid=%d' % cmd[1:])
+
+            elif a[0] == 'write':
+                print_actions.append('write: from=%d to=%d' % cmd[1:])
 
-      elif a[0] == 'symlink':
-         print_actions.append('symlink to "%s"' % cmd[1])
-
-      elif a[0] in ('unlink', 'mkfile', 'mkdir', 'mkfifo'):
-         print_actions.append('%s' % a[0])
-
-      elif a[0] == 'rename':
-         print_actions.append('rename to "%s"' % cmd[2])
-
-      elif a[0] == 'utimes':
-         print_actions.append('times a=%s m=%s c=%s' % (
-            time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[1])),
-            time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[2])),
-            time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(cmd[3]))
-            ))
-
-      elif a[0] == 'snapshot':
-         print_actions.append('snapshot: uuid=%s, ctrasid=%d, clone_uuid=%s, clone_ctransid=%d' % cmd[1:])
-
-      else:
-         print_actions.append('%s, %s %s' % (a, cmd, '-' * 20))
-      prev_act = a[0]
-
-   print ';'.join(print_actions)
+            else:
+                print_actions.append('%s, %s %s' % (a, cmd, '-' * 20))
+            prev_act = a[0]
 
+        print ';'.join(print_actions)