Add a tool to list contents of .img file

list_image <img> lists the contents of the file.

For example:
  $ m microdroid
  $ list_image <path to microdroid.img>
  ./
  ./apex/
  ./bin
  ..

Bug: 195425111
Bug: 225121718
Test: list_image .img
Change-Id: I7c4fca184751ba20066fd25fa4c366a955dbabf6
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 4c847a1..a694b95 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -188,3 +188,11 @@
         "get_clang_version.py",
     ],
 }
+
+python_binary_host {
+    name: "list_image",
+    main: "list_image.py",
+    srcs: [
+        "list_image.py",
+    ],
+}
diff --git a/scripts/list_image.py b/scripts/list_image.py
new file mode 100644
index 0000000..7f76537
--- /dev/null
+++ b/scripts/list_image.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""list_image is a tool that prints out content of an .img file.
+
+To print content of an image to stdout:
+  list_image foo.img
+"""
+from __future__ import print_function
+
+import argparse
+import os
+import sys
+import subprocess
+
+class ImageEntry(object):
+
+  def __init__(self, name, base_dir, is_directory=False):
+    self._name = name
+    self._base_dir = base_dir
+    self._is_directory = is_directory
+
+  @property
+  def name(self):
+    return self._name
+
+  @property
+  def full_path(self):
+    return os.path.join(self._base_dir, self._name)
+
+  @property
+  def is_directory(self):
+    return self._is_directory
+
+
+class Image(object):
+
+  def __init__(self, debugfs_path, img_path):
+    self._debugfs = debugfs_path
+    self._img_path = img_path
+
+  def list(self, path):
+    print(path)
+    process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % path, self._img_path],
+                               stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                               universal_newlines=True)
+    stdout, _ = process.communicate()
+    res = str(stdout)
+    entries = []
+    for line in res.split('\n'):
+      if not line:
+        continue
+      parts = line.split('/')
+      if len(parts) != 8:
+        continue
+      name = parts[5]
+      if not name:
+        continue
+      bits = parts[2]
+      is_directory = bits[1] == '4'
+      entries.append(ImageEntry(name, path, is_directory))
+
+    for e in sorted(entries, key=lambda e: e.name):
+      yield e
+      if e.is_directory and e.name != '.' and e.name != '..':
+        yield from self.list(path + e.name + '/')
+
+
+def main(argv):
+  parser = argparse.ArgumentParser()
+
+  debugfs_default = None
+  if 'ANDROID_HOST_OUT' in os.environ:
+    debugfs_default = '%s/bin/debugfs_static' % os.environ['ANDROID_HOST_OUT']
+  parser.add_argument('--debugfs_path', help='The path to debugfs binary', default=debugfs_default)
+  parser.add_argument('img_path', type=str, help='.img file')
+  args = parser.parse_args(argv)
+
+  if not args.debugfs_path:
+    print('ANDROID_HOST_OUT environment variable is not defined, --debugfs_path must be set',
+          file=sys.stderr)
+    sys.exit(1)
+
+  for e in Image(args.debugfs_path, args.img_path).list('./'):
+    if e.is_directory:
+      continue
+    print(e.full_path)
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])