blob: 134a026d295caa48a05e0ac09e6f70ed945ca4e1 [file] [log] [blame]
Mike Frysinger694a34e2020-01-30 14:05:07 -05001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
Mike Frysinger8cbb3762015-04-19 01:15:04 -04003# Copyright 2015 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Helper tools related to the layout text file.
8
9First create a directory with the paths in it:
10$ %(progs)s make common/fs-layout.txt stagedir/
11
12Then create a reduced layout for later inclusion:
13$ %(progs)s filter common/fs-layout.txt new-layout.txt
14"""
15
16from __future__ import print_function
17
18import argparse
19import errno
20import os
21import sys
22
23
Mike Frysinger8cbb3762015-04-19 01:15:04 -040024def symlink(src, dst):
25 """Like os.symlink, but handle existing errors"""
26 try:
27 os.symlink(src, dst)
28 return
29 except OSError as e:
30 if e.errno != errno.EEXIST:
31 raise
32 # Assume the symlink has changed to make our lives simple.
33 os.unlink(dst)
34 os.symlink(src, dst)
35
36
37def ProcessLayout(layout):
38 """Yield each valid line in |layout| as a tuple of each element"""
39 # The number of elements expected for each object type.
40 valid_lens = {
41 'file': (6, 7),
42 'dir': (5,),
43 'nod': (8,),
44 'slink': (6,),
45 'pipe': (5,),
46 'sock': (5,),
47 }
48
Mike Frysinger694a34e2020-01-30 14:05:07 -050049 with open(layout, encoding='utf-8') as f:
Mike Frysinger8cbb3762015-04-19 01:15:04 -040050 for line in f:
51 line = line.split('#', 1)[0].strip()
52 if not line:
53 continue
54
55 elements = line.split()
56
57 etype = elements[0]
58 if etype not in valid_lens:
59 raise ValueError('Invalid line: unknown type "%s":\n%s' %
60 (etype, line))
61
62 valid_len = valid_lens[etype]
63 if len(elements) not in valid_len:
64 raise ValueError('Invalid line: wanted %r elements; got %i:\n%s' %
65 (valid_len, len(elements), line))
66
67 yield elements
68
69
70def GetParser():
71 parser = argparse.ArgumentParser(description=__doc__)
72 parser.add_argument('mode', choices=('make', 'filter'),
73 help='operation to perform')
74 parser.add_argument('layout', help='path to the filesystem layout file')
75 parser.add_argument('output', help='path to operate on')
76 return parser
77
78
79def main(argv):
80 parser = GetParser()
81 opts = parser.parse_args(argv)
82
83 if opts.mode == 'make':
84 # Create all the requested directories/files in the output directory.
85 # These paths are needed so we can install all files into the right
86 # layout w/out creating conflicts (e.g. /usr being a dir or a symlink).
87 for elements in ProcessLayout(opts.layout):
88 etype = elements.pop(0)
89 try:
90 if etype == 'dir':
91 path, mode, uid, gid = elements
92 assert ('0', '0') == (uid, gid)
93 mode = int(mode, 8)
94 path = os.path.join(opts.output, path.lstrip('/'))
Mike Frysinger694a34e2020-01-30 14:05:07 -050095 os.makedirs(path, exist_ok=True)
Mike Frysinger8cbb3762015-04-19 01:15:04 -040096 os.chmod(path, mode)
97 elif etype == 'slink':
98 path, target, mode, uid, gid = elements
99 mode = int(mode, 8)
100 assert ('0', '0', 0o755) == (uid, gid, mode)
101 path = os.path.join(opts.output, path.lstrip('/'))
Mike Frysinger694a34e2020-01-30 14:05:07 -0500102 os.makedirs(os.path.dirname(path), exist_ok=True)
Mike Frysinger8cbb3762015-04-19 01:15:04 -0400103 symlink(target, path)
104 except Exception:
105 print('While processing line: %s %s' % (etype, elements))
106 raise
107
108 elif opts.mode == 'filter':
109 # Filter out all the paths that 'make' above created. The stuff that is
110 # left often requires root access (which we don't have), but the cpio gen
111 # tool can take care of this for us.
Mike Frysinger694a34e2020-01-30 14:05:07 -0500112 with open(opts.output, 'a', encoding='utf-8') as f:
Mike Frysinger8cbb3762015-04-19 01:15:04 -0400113 for elements in ProcessLayout(opts.layout):
114 if elements[0] not in ('dir', 'slink'):
115 f.write(' '.join(elements) + '\n')
116
117
118if __name__ == '__main__':
119 main(sys.argv[1:])