unify the basic fs layout using the initramfs txt

Rather than manually `mkdir` and `ln` every path we care about, use a
flat text file to describe things.  We still create dirs we want to
install into, but the rest are now done by the generation tool.

BUG=brillo:845
TEST=`make` still builds all the targets
TEST=`USE='recovery_ramfs factory_shim_ramfs' emerge-link chromeos-initramfs` works
TEST=comparing contents files looks OK

Change-Id: I7edb24d288619c0b62423487b9b9b647d3439cee
Reviewed-on: https://chromium-review.googlesource.com/266273
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Commit-Queue: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/common/process-layout.py b/common/process-layout.py
new file mode 100755
index 0000000..2cb119c
--- /dev/null
+++ b/common/process-layout.py
@@ -0,0 +1,127 @@
+#!/usr/bin/python2
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Helper tools related to the layout text file.
+
+First create a directory with the paths in it:
+$ %(progs)s make common/fs-layout.txt stagedir/
+
+Then create a reduced layout for later inclusion:
+$ %(progs)s filter common/fs-layout.txt new-layout.txt
+"""
+
+from __future__ import print_function
+
+import argparse
+import errno
+import os
+import sys
+
+
+def makedirs(path):
+  """Like os.makedirs, but ignore existing errors"""
+  try:
+    os.makedirs(path)
+  except OSError as e:
+    if e.errno != errno.EEXIST:
+      raise
+
+
+def symlink(src, dst):
+  """Like os.symlink, but handle existing errors"""
+  try:
+    os.symlink(src, dst)
+    return
+  except OSError as e:
+    if e.errno != errno.EEXIST:
+      raise
+    # Assume the symlink has changed to make our lives simple.
+    os.unlink(dst)
+    os.symlink(src, dst)
+
+
+def ProcessLayout(layout):
+  """Yield each valid line in |layout| as a tuple of each element"""
+  # The number of elements expected for each object type.
+  valid_lens = {
+      'file': (6, 7),
+      'dir': (5,),
+      'nod': (8,),
+      'slink': (6,),
+      'pipe': (5,),
+      'sock': (5,),
+  }
+
+  with open(layout) as f:
+    for line in f:
+      line = line.split('#', 1)[0].strip()
+      if not line:
+        continue
+
+      elements = line.split()
+
+      etype = elements[0]
+      if etype not in valid_lens:
+        raise ValueError('Invalid line: unknown type "%s":\n%s' %
+                         (etype, line))
+
+      valid_len = valid_lens[etype]
+      if len(elements) not in valid_len:
+        raise ValueError('Invalid line: wanted %r elements; got %i:\n%s' %
+                         (valid_len, len(elements), line))
+
+      yield elements
+
+
+def GetParser():
+  parser = argparse.ArgumentParser(description=__doc__)
+  parser.add_argument('mode', choices=('make', 'filter'),
+                      help='operation to perform')
+  parser.add_argument('layout', help='path to the filesystem layout file')
+  parser.add_argument('output', help='path to operate on')
+  return parser
+
+
+def main(argv):
+  parser = GetParser()
+  opts = parser.parse_args(argv)
+
+  if opts.mode == 'make':
+    # Create all the requested directories/files in the output directory.
+    # These paths are needed so we can install all files into the right
+    # layout w/out creating conflicts (e.g. /usr being a dir or a symlink).
+    for elements in ProcessLayout(opts.layout):
+      etype = elements.pop(0)
+      try:
+        if etype == 'dir':
+          path, mode, uid, gid = elements
+          assert ('0', '0') == (uid, gid)
+          mode = int(mode, 8)
+          path = os.path.join(opts.output, path.lstrip('/'))
+          makedirs(path)
+          os.chmod(path, mode)
+        elif etype == 'slink':
+          path, target, mode, uid, gid = elements
+          mode = int(mode, 8)
+          assert ('0', '0', 0o755) == (uid, gid, mode)
+          path = os.path.join(opts.output, path.lstrip('/'))
+          makedirs(os.path.dirname(path))
+          symlink(target, path)
+      except Exception:
+        print('While processing line: %s %s' % (etype, elements))
+        raise
+
+  elif opts.mode == 'filter':
+    # Filter out all the paths that 'make' above created.  The stuff that is
+    # left often requires root access (which we don't have), but the cpio gen
+    # tool can take care of this for us.
+    with open(opts.output, 'a') as f:
+      for elements in ProcessLayout(opts.layout):
+        if elements[0] not in ('dir', 'slink'):
+          f.write(' '.join(elements) + '\n')
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])