new matrix-rootfs script replaces the "rootfs" action of matrix
[matrix.git] / matrix / rootfs.py
1 # Copyright (C) 2007-2008 Movial Oy
2 # Authors: Kalle Vahlman <kalle.vahlman@movial.fi>
3 #          Timo Savola <tsavola@movial.fi>
4
5 import errno
6 import optparse
7 import os
8 import re
9 import shutil
10 import sys
11
12 import config
13
14 supported_types = (
15         'jffs2',
16 )
17 default_type = 'jffs2'
18
19 def main():
20         parser = optparse.OptionParser(usage='%s [<options>]' % sys.argv[0])
21
22         parser.add_option(
23                 '-n', '--no-clean',
24                 help='do not clean env.faked and stripped directory',
25                 dest='clean', action='store_false', default=True)
26
27         parser.add_option(
28                 '-i', '--image-only',
29                 help='generate only a filesystem image',
30                 dest='image_only', action='store_true')
31
32         parser.add_option(
33                 '-T', '--image-type',
34                 help='type of generated filesystem image [%s]' % default_type,
35                 dest='type', choices=supported_types, default=default_type)
36
37         parser.add_option(
38                 '-r', '--rootfs-only',
39                 help='generate only a stripped archive',
40                 dest='rootfs_only', action='store_true')
41
42         parser.add_option(
43                 '-d', '--devrootfs-only',
44                 help='generate only an archive with development files',
45                 dest='devrootfs_only', action='store_true')
46
47         opts, args = parser.parse_args()
48         if args:
49                 parser.print_help()
50                 sys.exit(1)
51
52         config.parse('config.local')
53         config.parse('config')
54         config.parse('boards')
55         config.parse('rootfs')
56
57         f = os.popen('sb-conf cu')
58         target = f.read()
59         f.close()
60
61         build = Builder(target.strip())
62         build.include_paths(config.include_paths)
63         build.include_files(config.include_files)
64         build.filter_paths(config.exclude_paths)
65         build.filter_files(config.exclude_files)
66         build.filter_expressions(config.exclude_expressions)
67         build.add_paths(config.created_paths)
68         build.set_devices(config.devices)
69         build.set_change_owner(config.change_owner)
70         build.set_erase_size(config.boards[config.board].flash_erase_size)
71         build.set_pad_size(config.boards[config.board].flash_pad_size)
72
73         if opts.rootfs_only:
74                 build.generate(opts.clean, build_target="rootfs")
75         elif opts.image_only:
76                 build.generate(opts.clean, build_target=opts.type)
77         elif opts.devrootfs_only:
78                 build.generate(opts.clean, build_target="devrootfs")
79         else:
80                 build.generate(opts.clean, build_target="all")
81
82 class Builder(object):
83         builddir = "/tmp"
84         target = None
85
86         # Tools
87         strip = "strip"
88         mknod = "fakeroot -i /tmp/env.faked -s /tmp/env.faked mknod"
89         chmod = "fakeroot -i /tmp/env.faked -s /tmp/env.faked chmod"
90         chown = "fakeroot -i /tmp/env.faked -s /tmp/env.faked chown"
91         mkfs = "fakeroot -i /tmp/env.faked -s /tmp/env.faked mkfs.jffs2"
92         tar = "fakeroot -i /tmp/env.faked -s /tmp/env.faked tar"
93         flash_erase_size = "0x4000"
94         flash_pad_size = "0x3e00000"
95
96         file_list = []
97         remove_list = []
98
99         created_paths = []
100         devices = {}
101         chown_owner = {}
102
103         def __init__(self, mytarget):
104                 self.target = mytarget
105
106         def include_paths(self, paths):
107                 old_dir = os.getcwd()
108                 os.chdir("/targets")
109
110                 # Traverse include_paths hierarchies and grab all files there
111                 for path in paths:
112                         for root, dirs, files in os.walk(self.target + path):
113                                 for f in files:
114                                         self.file_list.append("%s/%s" % (root, f))
115                                 if len(files) == 0:
116                                         self.file_list.append(root)
117
118                 os.chdir(old_dir)
119
120         def include_files(self, files):
121                 old_dir = os.getcwd()
122                 os.chdir("/targets")
123
124                 # Append included individual files
125                 for f in files:
126                         self.file_list.append(self.target + f)
127
128                 os.chdir(old_dir)
129
130         def filter_paths(self, paths):
131                 # Filter out files in exluded paths
132                 for f in self.file_list:
133                         for d in paths:
134                                 if f == self.target + d or f.startswith(self.target + d + '/'):
135                                         self.remove_list.append(f)
136
137         def filter_files(self, files):
138                 # Filter out exluded files
139                 for f in files:
140                         self.remove_list.append(self.target + f)
141
142         def filter_expressions(self, expressions):
143                 # Filter out files matching exluded patterns
144                 compiled_patterns = []
145                 for pattern_str in expressions:
146                         compiled_patterns.append(re.compile(pattern_str))
147
148                 for f in self.file_list:
149                         for pattern in compiled_patterns:
150                                 if pattern.search(f):
151                                         self.remove_list.append(f)
152
153         def add_paths(self, paths):
154                 for path in paths:
155                         self.created_paths.append(path)
156
157         def set_devices(self, devices):
158                 self.devices = devices
159
160         def set_change_owner(self, change_owner):
161                 self.change_owner= change_owner
162
163         def set_erase_size(self, size):
164                 self.flash_erase_size = size
165
166         def set_pad_size(self, size):
167                 self.flash_pad_size = size
168
169         def generate(self, clean, build_target="all"):
170                 copy_list = self.file_list
171
172                 for f in self.remove_list:
173                                 try:
174                                         copy_list.remove(f)
175                                 except:
176                                         pass
177
178                 n_files = len(copy_list)*1.0
179                 current_percent = 0.0
180                 current_file = 0.0
181
182                 old_dir = os.getcwd()
183                 os.chdir("/targets")
184
185                 if os.path.exists(os.path.join(self.builddir, self.target)):
186                         print "%s exists, please remove it and try again" % os.path.join(self.builddir, self.target)
187                         return
188
189                 # Copy the wanted files, strip binaries
190                 print "Copying files..."
191                 for f in copy_list:
192                         if f[0] == '/':
193                                 f = f[1:]
194                         path = os.path.join(self.builddir, os.path.dirname(f))
195                         try:
196                                 os.makedirs(path)
197                         except OSError, e:
198                                 if e.errno == errno.EEXIST:
199                                         pass
200
201                         if os.path.islink(f):
202                                 link_dest = os.readlink(f)
203                                 os.symlink(link_dest, os.path.join(self.builddir, f))
204                         elif os.path.isdir(f):
205                                 try:
206                                         os.makedirs(os.path.join(self.builddir, f))
207                                 except OSError, e:
208                                         if e.errno == errno.EEXIST:
209                                                 pass
210                         else:
211                                 shutil.copy(f, path)
212                                 if not f.endswith(".ko"):
213                                         os.system(self.strip + " " + os.path.join(path,os.path.basename(f)) + " &> /dev/null")
214
215                         if (current_file/n_files)*100 > current_percent:
216                                 current_percent += 10.0
217                                 print "%0.1f%%" % current_percent,
218                                 sys.stdout.flush()
219
220                         current_file += 1.0
221
222                 # Create extra directory structure
223                 print "\nCreating extra directories..."
224                 for d in self.created_paths:
225                         if d[0] == '/':
226                                 d = d[1:]
227                         path = os.path.join(self.builddir, self.target, d)
228                         try:
229                                 os.makedirs(path)
230                         except OSError, e:
231                                 if e.errno == errno.EEXIST:
232                                         pass
233
234                 # Remove always the fakeroot environment.
235                 if os.path.exists("/tmp/env.faked"):
236                         os.remove("/tmp/env.faked")
237                 os.system("touch /tmp/env.faked")
238
239                 # Create device nodes
240                 print "Creating device nodes..."
241                 for d in self.devices.keys():
242                         mode = "664"
243                         if len(self.devices[d]) == 3:
244                                 device_type,major,minor = self.devices[d]
245                         else:
246                                 device_type,major,minor,mode = self.devices[d]
247                         os.system("%s -m %s %s/%s/dev/%s %s %i %i" % (self.mknod, mode, self.builddir, self.target, d, device_type, major, minor))
248
249                 # Fix permissions
250                 print "Adjusting ownerships and permissions..."
251                 # Make the whole fs root-owned
252                 os.system("%s root.root -R %s/%s" % (self.chown, self.builddir, self.target))
253
254                 # For sshd:
255                 os.system("%s go-r %s/%s/etc/ssh*key*" % (self.chmod, self.builddir, self.target))
256
257                 # For /var/empty (sshd requires this):
258                 os.system("%s go-rw /%s/%s/var/empty" % (self.chmod, self.builddir, self.target))
259
260                 # /dev/null needs to be writable by all:
261                 os.system("%s a+rw /%s/%s/dev/null" % (self.chmod, self.builddir, self.target))
262
263                 # Set non-root owners
264                 print "Setting non-root owner ships..."
265                 for d in self.change_owner.keys():
266                         dir = d
267                         if dir[0] == '/':
268                                 dir = dir[1:]
269                         if os.path.exists(os.path.join(self.builddir, self.target, dir)):
270                                 user,group = self.change_owner[d]
271                                 os.system("%s %s.%s -R %s/%s/%s " % (self.chown, user, group, self.builddir, self.target, dir))
272
273                 if build_target is "all" or build_target is "rootfs":
274                         print "Creating a rootfs..."
275                         os.system("%s -c --one-file-system -C %s/%s -z -f %s/%s.tgz ." % (self.tar, self.builddir, self.target, self.builddir, self.target))
276                 if build_target is "all" or build_target is "jffs2":
277                         print "Creating a root image..."
278                         os.system("%s -p%s -n -e%s -r %s/%s -o %s/%s.jffs2" % (self.mkfs, self.flash_pad_size, self.flash_erase_size, self.builddir, self.target, self.builddir, self.target))
279                 if build_target is "all" or build_target is "devrootfs":
280                         print "Creating a rootstrap..."
281                         os.system("%s -c --one-file-system -C /targets/%s -z -f %s/%s-rootstrap.tgz ." % ("tar", self.target, self.builddir, self.target))
282
283                 os.chdir(old_dir)
284
285                 print "Build finished:"
286                 if build_target is "all" or build_target is "rootfs":
287                         s = os.stat(self.builddir + "/" + self.target + ".tgz");
288                         print "\t%s/%s.tgz\t%0.2f MiB" % (self.builddir, self.target, s.st_size/1024.0/1024.0)
289                 if build_target is "all" or build_target is "jffs2":
290                         s = os.stat(self.builddir + "/" + self.target + ".jffs2");
291                         print "\t%s/%s.jffs2\t%0.2f MiB" % (self.builddir, self.target, s.st_size/1024.0/1024.0)
292                 if build_target is "all" or build_target is "devrootfs":
293                         s = os.stat(self.builddir + "/" + self.target + "-rootstrap.tgz");
294                         print "\t%s/%s-rootstrap.tgz\t%0.2f MiB" % (self.builddir, self.target, s.st_size/1024.0/1024.0)
295
296                 if clean:
297                         print "Cleaning up"
298                         os.remove("/tmp/env.faked")
299                         os.system("rm -rf %s/%s" % (self.builddir, self.target))