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