summaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/wic')
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/__init__.py0
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/base.py466
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/commands/__init__.py20
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/commands/bootloader.py216
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/commands/partition.py314
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/constants.py57
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/errors.py103
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/handlers/__init__.py0
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/handlers/control.py46
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/handlers/f16.py24
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/ko.py37
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/options.py204
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/parser.py619
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/sections.py244
-rw-r--r--scripts/lib/wic/3rdparty/pykickstart/version.py168
-rw-r--r--scripts/lib/wic/__init__.py4
-rw-r--r--scripts/lib/wic/__version__.py1
-rw-r--r--scripts/lib/wic/conf.py102
-rw-r--r--scripts/lib/wic/creator.py187
-rw-r--r--scripts/lib/wic/imager/__init__.py0
-rw-r--r--scripts/lib/wic/imager/baseimager.py193
-rw-r--r--scripts/lib/wic/imager/direct.py363
-rw-r--r--scripts/lib/wic/kickstart/__init__.py125
-rw-r--r--scripts/lib/wic/kickstart/custom_commands/__init__.py10
-rw-r--r--scripts/lib/wic/kickstart/custom_commands/micboot.py49
-rw-r--r--scripts/lib/wic/kickstart/custom_commands/micpartition.py57
-rw-r--r--scripts/lib/wic/kickstart/custom_commands/partition.py652
-rw-r--r--scripts/lib/wic/kickstart/custom_commands/wicboot.py57
-rw-r--r--scripts/lib/wic/msger.py309
-rw-r--r--scripts/lib/wic/plugin.py156
-rw-r--r--scripts/lib/wic/pluginbase.py120
-rw-r--r--scripts/lib/wic/plugins/imager/direct_plugin.py98
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-efi.py236
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-partition.py115
-rw-r--r--scripts/lib/wic/plugins/source/bootimg-pcbios.py200
-rw-r--r--scripts/lib/wic/plugins/source/rootfs.py92
-rw-r--r--scripts/lib/wic/plugins/source/uboot.py173
-rw-r--r--scripts/lib/wic/test1
-rw-r--r--scripts/lib/wic/utils/__init__.py0
-rw-r--r--scripts/lib/wic/utils/cmdln.py1586
-rw-r--r--scripts/lib/wic/utils/errors.py47
-rw-r--r--scripts/lib/wic/utils/fs_related.py111
-rw-r--r--scripts/lib/wic/utils/misc.py59
-rw-r--r--scripts/lib/wic/utils/oe/__init__.py22
-rw-r--r--scripts/lib/wic/utils/oe/misc.py205
-rw-r--r--scripts/lib/wic/utils/oe/package_manager.py810
-rw-r--r--scripts/lib/wic/utils/partitionedfs.py360
-rw-r--r--scripts/lib/wic/utils/runner.py109
48 files changed, 9127 insertions, 0 deletions
diff --git a/scripts/lib/wic/3rdparty/pykickstart/__init__.py b/scripts/lib/wic/3rdparty/pykickstart/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/__init__.py
diff --git a/scripts/lib/wic/3rdparty/pykickstart/base.py b/scripts/lib/wic/3rdparty/pykickstart/base.py
new file mode 100644
index 0000000000..e6c8f56f9d
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/base.py
@@ -0,0 +1,466 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2006, 2007, 2008 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20"""
21Base classes for creating commands and syntax version object.
22
23This module exports several important base classes:
24
25 BaseData - The base abstract class for all data objects. Data objects
26 are contained within a BaseHandler object.
27
28 BaseHandler - The base abstract class from which versioned kickstart
29 handler are derived. Subclasses of BaseHandler hold
30 BaseData and KickstartCommand objects.
31
32 DeprecatedCommand - An abstract subclass of KickstartCommand that should
33 be further subclassed by users of this module. When
34 a subclass is used, a warning message will be
35 printed.
36
37 KickstartCommand - The base abstract class for all kickstart commands.
38 Command objects are contained within a BaseHandler
39 object.
40"""
41import gettext
42gettext.textdomain("pykickstart")
43_ = lambda x: gettext.ldgettext("pykickstart", x)
44
45import types
46import warnings
47from pykickstart.errors import *
48from pykickstart.ko import *
49from pykickstart.parser import Packages
50from pykickstart.version import versionToString
51
52###
53### COMMANDS
54###
55class KickstartCommand(KickstartObject):
56 """The base class for all kickstart commands. This is an abstract class."""
57 removedKeywords = []
58 removedAttrs = []
59
60 def __init__(self, writePriority=0, *args, **kwargs):
61 """Create a new KickstartCommand instance. This method must be
62 provided by all subclasses, but subclasses must call
63 KickstartCommand.__init__ first. Instance attributes:
64
65 currentCmd -- The name of the command in the input file that
66 caused this handler to be run.
67 currentLine -- The current unprocessed line from the input file
68 that caused this handler to be run.
69 handler -- A reference to the BaseHandler subclass this
70 command is contained withing. This is needed to
71 allow referencing of Data objects.
72 lineno -- The current line number in the input file.
73 writePriority -- An integer specifying when this command should be
74 printed when iterating over all commands' __str__
75 methods. The higher the number, the later this
76 command will be written. All commands with the
77 same priority will be written alphabetically.
78 """
79
80 # We don't want people using this class by itself.
81 if self.__class__ is KickstartCommand:
82 raise TypeError, "KickstartCommand is an abstract class."
83
84 KickstartObject.__init__(self, *args, **kwargs)
85
86 self.writePriority = writePriority
87
88 # These will be set by the dispatcher.
89 self.currentCmd = ""
90 self.currentLine = ""
91 self.handler = None
92 self.lineno = 0
93
94 # If a subclass provides a removedKeywords list, remove all the
95 # members from the kwargs list before we start processing it. This
96 # ensures that subclasses don't continue to recognize arguments that
97 # were removed.
98 for arg in filter(kwargs.has_key, self.removedKeywords):
99 kwargs.pop(arg)
100
101 def __call__(self, *args, **kwargs):
102 """Set multiple attributes on a subclass of KickstartCommand at once
103 via keyword arguments. Valid attributes are anything specified in
104 a subclass, but unknown attributes will be ignored.
105 """
106 for (key, val) in kwargs.items():
107 # Ignore setting attributes that were removed in a subclass, as
108 # if they were unknown attributes.
109 if key in self.removedAttrs:
110 continue
111
112 if hasattr(self, key):
113 setattr(self, key, val)
114
115 def __str__(self):
116 """Return a string formatted for output to a kickstart file. This
117 method must be provided by all subclasses.
118 """
119 return KickstartObject.__str__(self)
120
121 def parse(self, args):
122 """Parse the list of args and set data on the KickstartCommand object.
123 This method must be provided by all subclasses.
124 """
125 raise TypeError, "parse() not implemented for KickstartCommand"
126
127 def apply(self, instroot="/"):
128 """Write out the configuration related to the KickstartCommand object.
129 Subclasses which do not provide this method will not have their
130 configuration written out.
131 """
132 return
133
134 def dataList(self):
135 """For commands that can occur multiple times in a single kickstart
136 file (like network, part, etc.), return the list that we should
137 append more data objects to.
138 """
139 return None
140
141 def deleteRemovedAttrs(self):
142 """Remove all attributes from self that are given in the removedAttrs
143 list. This method should be called from __init__ in a subclass,
144 but only after the superclass's __init__ method has been called.
145 """
146 for attr in filter(lambda k: hasattr(self, k), self.removedAttrs):
147 delattr(self, attr)
148
149 # Set the contents of the opts object (an instance of optparse.Values
150 # returned by parse_args) as attributes on the KickstartCommand object.
151 # It's useful to call this from KickstartCommand subclasses after parsing
152 # the arguments.
153 def _setToSelf(self, optParser, opts):
154 self._setToObj(optParser, opts, self)
155
156 # Sets the contents of the opts object (an instance of optparse.Values
157 # returned by parse_args) as attributes on the provided object obj. It's
158 # useful to call this from KickstartCommand subclasses that handle lists
159 # of objects (like partitions, network devices, etc.) and need to populate
160 # a Data object.
161 def _setToObj(self, optParser, opts, obj):
162 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
163 setattr(obj, key, getattr(opts, key))
164
165class DeprecatedCommand(KickstartCommand):
166 """Specify that a command is deprecated and no longer has any function.
167 Any command that is deprecated should be subclassed from this class,
168 only specifying an __init__ method that calls the superclass's __init__.
169 This is an abstract class.
170 """
171 def __init__(self, writePriority=None, *args, **kwargs):
172 # We don't want people using this class by itself.
173 if self.__class__ is KickstartCommand:
174 raise TypeError, "DeprecatedCommand is an abstract class."
175
176 # Create a new DeprecatedCommand instance.
177 KickstartCommand.__init__(self, writePriority, *args, **kwargs)
178
179 def __str__(self):
180 """Placeholder since DeprecatedCommands don't work anymore."""
181 return ""
182
183 def parse(self, args):
184 """Print a warning message if the command is seen in the input file."""
185 mapping = {"lineno": self.lineno, "cmd": self.currentCmd}
186 warnings.warn(_("Ignoring deprecated command on line %(lineno)s: The %(cmd)s command has been deprecated and no longer has any effect. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to remove this command.") % mapping, DeprecationWarning)
187
188
189###
190### HANDLERS
191###
192class BaseHandler(KickstartObject):
193 """Each version of kickstart syntax is provided by a subclass of this
194 class. These subclasses are what users will interact with for parsing,
195 extracting data, and writing out kickstart files. This is an abstract
196 class.
197
198 version -- The version this syntax handler supports. This is set by
199 a class attribute of a BaseHandler subclass and is used to
200 set up the command dict. It is for read-only use.
201 """
202 version = None
203
204 def __init__(self, mapping=None, dataMapping=None, commandUpdates=None,
205 dataUpdates=None, *args, **kwargs):
206 """Create a new BaseHandler instance. This method must be provided by
207 all subclasses, but subclasses must call BaseHandler.__init__ first.
208
209 mapping -- A custom map from command strings to classes,
210 useful when creating your own handler with
211 special command objects. It is otherwise unused
212 and rarely needed. If you give this argument,
213 the mapping takes the place of the default one
214 and so must include all commands you want
215 recognized.
216 dataMapping -- This is the same as mapping, but for data
217 objects. All the same comments apply.
218 commandUpdates -- This is similar to mapping, but does not take
219 the place of the defaults entirely. Instead,
220 this mapping is applied after the defaults and
221 updates it with just the commands you want to
222 modify.
223 dataUpdates -- This is the same as commandUpdates, but for
224 data objects.
225
226
227 Instance attributes:
228
229 commands -- A mapping from a string command to a KickstartCommand
230 subclass object that handles it. Multiple strings can
231 map to the same object, but only one instance of the
232 command object should ever exist. Most users should
233 never have to deal with this directly, as it is
234 manipulated internally and called through dispatcher.
235 currentLine -- The current unprocessed line from the input file
236 that caused this handler to be run.
237 packages -- An instance of pykickstart.parser.Packages which
238 describes the packages section of the input file.
239 platform -- A string describing the hardware platform, which is
240 needed only by system-config-kickstart.
241 scripts -- A list of pykickstart.parser.Script instances, which is
242 populated by KickstartParser.addScript and describes the
243 %pre/%post/%traceback script section of the input file.
244 """
245
246 # We don't want people using this class by itself.
247 if self.__class__ is BaseHandler:
248 raise TypeError, "BaseHandler is an abstract class."
249
250 KickstartObject.__init__(self, *args, **kwargs)
251
252 # This isn't really a good place for these, but it's better than
253 # everything else I can think of.
254 self.scripts = []
255 self.packages = Packages()
256 self.platform = ""
257
258 # These will be set by the dispatcher.
259 self.commands = {}
260 self.currentLine = 0
261
262 # A dict keyed by an integer priority number, with each value being a
263 # list of KickstartCommand subclasses. This dict is maintained by
264 # registerCommand and used in __str__. No one else should be touching
265 # it.
266 self._writeOrder = {}
267
268 self._registerCommands(mapping, dataMapping, commandUpdates, dataUpdates)
269
270 def __str__(self):
271 """Return a string formatted for output to a kickstart file."""
272 retval = ""
273
274 if self.platform != "":
275 retval += "#platform=%s\n" % self.platform
276
277 retval += "#version=%s\n" % versionToString(self.version)
278
279 lst = self._writeOrder.keys()
280 lst.sort()
281
282 for prio in lst:
283 for obj in self._writeOrder[prio]:
284 retval += obj.__str__()
285
286 for script in self.scripts:
287 retval += script.__str__()
288
289 retval += self.packages.__str__()
290
291 return retval
292
293 def _insertSorted(self, lst, obj):
294 length = len(lst)
295 i = 0
296
297 while i < length:
298 # If the two classes have the same name, it's because we are
299 # overriding an existing class with one from a later kickstart
300 # version, so remove the old one in favor of the new one.
301 if obj.__class__.__name__ > lst[i].__class__.__name__:
302 i += 1
303 elif obj.__class__.__name__ == lst[i].__class__.__name__:
304 lst[i] = obj
305 return
306 elif obj.__class__.__name__ < lst[i].__class__.__name__:
307 break
308
309 if i >= length:
310 lst.append(obj)
311 else:
312 lst.insert(i, obj)
313
314 def _setCommand(self, cmdObj):
315 # Add an attribute on this version object. We need this to provide a
316 # way for clients to access the command objects. We also need to strip
317 # off the version part from the front of the name.
318 if cmdObj.__class__.__name__.find("_") != -1:
319 name = unicode(cmdObj.__class__.__name__.split("_", 1)[1])
320 else:
321 name = unicode(cmdObj.__class__.__name__).lower()
322
323 setattr(self, name.lower(), cmdObj)
324
325 # Also, add the object into the _writeOrder dict in the right place.
326 if cmdObj.writePriority is not None:
327 if self._writeOrder.has_key(cmdObj.writePriority):
328 self._insertSorted(self._writeOrder[cmdObj.writePriority], cmdObj)
329 else:
330 self._writeOrder[cmdObj.writePriority] = [cmdObj]
331
332 def _registerCommands(self, mapping=None, dataMapping=None, commandUpdates=None,
333 dataUpdates=None):
334 if mapping == {} or mapping == None:
335 from pykickstart.handlers.control import commandMap
336 cMap = commandMap[self.version]
337 else:
338 cMap = mapping
339
340 if dataMapping == {} or dataMapping == None:
341 from pykickstart.handlers.control import dataMap
342 dMap = dataMap[self.version]
343 else:
344 dMap = dataMapping
345
346 if type(commandUpdates) == types.DictType:
347 cMap.update(commandUpdates)
348
349 if type(dataUpdates) == types.DictType:
350 dMap.update(dataUpdates)
351
352 for (cmdName, cmdClass) in cMap.iteritems():
353 # First make sure we haven't instantiated this command handler
354 # already. If we have, we just need to make another mapping to
355 # it in self.commands.
356 cmdObj = None
357
358 for (key, val) in self.commands.iteritems():
359 if val.__class__.__name__ == cmdClass.__name__:
360 cmdObj = val
361 break
362
363 # If we didn't find an instance in self.commands, create one now.
364 if cmdObj == None:
365 cmdObj = cmdClass()
366 self._setCommand(cmdObj)
367
368 # Finally, add the mapping to the commands dict.
369 self.commands[cmdName] = cmdObj
370 self.commands[cmdName].handler = self
371
372 # We also need to create attributes for the various data objects.
373 # No checks here because dMap is a bijection. At least, that's what
374 # the comment says. Hope no one screws that up.
375 for (dataName, dataClass) in dMap.iteritems():
376 setattr(self, dataName, dataClass)
377
378 def dispatcher(self, args, lineno):
379 """Call the appropriate KickstartCommand handler for the current line
380 in the kickstart file. A handler for the current command should
381 be registered, though a handler of None is not an error. Returns
382 the data object returned by KickstartCommand.parse.
383
384 args -- A list of arguments to the current command
385 lineno -- The line number in the file, for error reporting
386 """
387 cmd = args[0]
388
389 if not self.commands.has_key(cmd):
390 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
391 elif self.commands[cmd] != None:
392 self.commands[cmd].currentCmd = cmd
393 self.commands[cmd].currentLine = self.currentLine
394 self.commands[cmd].lineno = lineno
395
396 # The parser returns the data object that was modified. This could
397 # be a BaseData subclass that should be put into a list, or it
398 # could be the command handler object itself.
399 obj = self.commands[cmd].parse(args[1:])
400 lst = self.commands[cmd].dataList()
401 if lst is not None:
402 lst.append(obj)
403
404 return obj
405
406 def maskAllExcept(self, lst):
407 """Set all entries in the commands dict to None, except the ones in
408 the lst. All other commands will not be processed.
409 """
410 self._writeOrder = {}
411
412 for (key, val) in self.commands.iteritems():
413 if not key in lst:
414 self.commands[key] = None
415
416 def hasCommand(self, cmd):
417 """Return true if there is a handler for the string cmd."""
418 return hasattr(self, cmd)
419
420
421###
422### DATA
423###
424class BaseData(KickstartObject):
425 """The base class for all data objects. This is an abstract class."""
426 removedKeywords = []
427 removedAttrs = []
428
429 def __init__(self, *args, **kwargs):
430 """Create a new BaseData instance.
431
432 lineno -- Line number in the ks-file where this object was defined
433 """
434
435 # We don't want people using this class by itself.
436 if self.__class__ is BaseData:
437 raise TypeError, "BaseData is an abstract class."
438
439 KickstartObject.__init__(self, *args, **kwargs)
440 self.lineno = 0
441
442 def __str__(self):
443 """Return a string formatted for output to a kickstart file."""
444 return ""
445
446 def __call__(self, *args, **kwargs):
447 """Set multiple attributes on a subclass of BaseData at once via
448 keyword arguments. Valid attributes are anything specified in a
449 subclass, but unknown attributes will be ignored.
450 """
451 for (key, val) in kwargs.items():
452 # Ignore setting attributes that were removed in a subclass, as
453 # if they were unknown attributes.
454 if key in self.removedAttrs:
455 continue
456
457 if hasattr(self, key):
458 setattr(self, key, val)
459
460 def deleteRemovedAttrs(self):
461 """Remove all attributes from self that are given in the removedAttrs
462 list. This method should be called from __init__ in a subclass,
463 but only after the superclass's __init__ method has been called.
464 """
465 for attr in filter(lambda k: hasattr(self, k), self.removedAttrs):
466 delattr(self, attr)
diff --git a/scripts/lib/wic/3rdparty/pykickstart/commands/__init__.py b/scripts/lib/wic/3rdparty/pykickstart/commands/__init__.py
new file mode 100644
index 0000000000..2d94550935
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/commands/__init__.py
@@ -0,0 +1,20 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2009 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20import bootloader, partition
diff --git a/scripts/lib/wic/3rdparty/pykickstart/commands/bootloader.py b/scripts/lib/wic/3rdparty/pykickstart/commands/bootloader.py
new file mode 100644
index 0000000000..c2b552f689
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/commands/bootloader.py
@@ -0,0 +1,216 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2007 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20from pykickstart.base import *
21from pykickstart.options import *
22
23class FC3_Bootloader(KickstartCommand):
24 removedKeywords = KickstartCommand.removedKeywords
25 removedAttrs = KickstartCommand.removedAttrs
26
27 def __init__(self, writePriority=10, *args, **kwargs):
28 KickstartCommand.__init__(self, writePriority, *args, **kwargs)
29 self.op = self._getParser()
30
31 self.driveorder = kwargs.get("driveorder", [])
32 self.appendLine = kwargs.get("appendLine", "")
33 self.forceLBA = kwargs.get("forceLBA", False)
34 self.linear = kwargs.get("linear", True)
35 self.location = kwargs.get("location", "")
36 self.md5pass = kwargs.get("md5pass", "")
37 self.password = kwargs.get("password", "")
38 self.upgrade = kwargs.get("upgrade", False)
39 self.useLilo = kwargs.get("useLilo", False)
40
41 self.deleteRemovedAttrs()
42
43 def _getArgsAsStr(self):
44 retval = ""
45
46 if self.appendLine != "":
47 retval += " --append=\"%s\"" % self.appendLine
48 if self.linear:
49 retval += " --linear"
50 if self.location:
51 retval += " --location=%s" % self.location
52 if hasattr(self, "forceLBA") and self.forceLBA:
53 retval += " --lba32"
54 if self.password != "":
55 retval += " --password=\"%s\"" % self.password
56 if self.md5pass != "":
57 retval += " --md5pass=\"%s\"" % self.md5pass
58 if self.upgrade:
59 retval += " --upgrade"
60 if self.useLilo:
61 retval += " --useLilo"
62 if len(self.driveorder) > 0:
63 retval += " --driveorder=\"%s\"" % ",".join(self.driveorder)
64
65 return retval
66
67 def __str__(self):
68 retval = KickstartCommand.__str__(self)
69
70 if self.location != "":
71 retval += "# System bootloader configuration\nbootloader"
72 retval += self._getArgsAsStr() + "\n"
73
74 return retval
75
76 def _getParser(self):
77 def driveorder_cb (option, opt_str, value, parser):
78 for d in value.split(','):
79 parser.values.ensure_value(option.dest, []).append(d)
80
81 op = KSOptionParser()
82 op.add_option("--append", dest="appendLine")
83 op.add_option("--linear", dest="linear", action="store_true",
84 default=True)
85 op.add_option("--nolinear", dest="linear", action="store_false")
86 op.add_option("--location", dest="location", type="choice",
87 default="mbr",
88 choices=["mbr", "partition", "none", "boot"])
89 op.add_option("--lba32", dest="forceLBA", action="store_true",
90 default=False)
91 op.add_option("--password", dest="password", default="")
92 op.add_option("--md5pass", dest="md5pass", default="")
93 op.add_option("--upgrade", dest="upgrade", action="store_true",
94 default=False)
95 op.add_option("--useLilo", dest="useLilo", action="store_true",
96 default=False)
97 op.add_option("--driveorder", dest="driveorder", action="callback",
98 callback=driveorder_cb, nargs=1, type="string")
99 return op
100
101 def parse(self, args):
102 (opts, extra) = self.op.parse_args(args=args, lineno=self.lineno)
103 self._setToSelf(self.op, opts)
104
105 if self.currentCmd == "lilo":
106 self.useLilo = True
107
108 return self
109
110class FC4_Bootloader(FC3_Bootloader):
111 removedKeywords = FC3_Bootloader.removedKeywords + ["linear", "useLilo"]
112 removedAttrs = FC3_Bootloader.removedAttrs + ["linear", "useLilo"]
113
114 def __init__(self, writePriority=10, *args, **kwargs):
115 FC3_Bootloader.__init__(self, writePriority, *args, **kwargs)
116
117 def _getArgsAsStr(self):
118 retval = ""
119 if self.appendLine != "":
120 retval += " --append=\"%s\"" % self.appendLine
121 if self.location:
122 retval += " --location=%s" % self.location
123 if hasattr(self, "forceLBA") and self.forceLBA:
124 retval += " --lba32"
125 if self.password != "":
126 retval += " --password=\"%s\"" % self.password
127 if self.md5pass != "":
128 retval += " --md5pass=\"%s\"" % self.md5pass
129 if self.upgrade:
130 retval += " --upgrade"
131 if len(self.driveorder) > 0:
132 retval += " --driveorder=\"%s\"" % ",".join(self.driveorder)
133 return retval
134
135 def _getParser(self):
136 op = FC3_Bootloader._getParser(self)
137 op.remove_option("--linear")
138 op.remove_option("--nolinear")
139 op.remove_option("--useLilo")
140 return op
141
142 def parse(self, args):
143 (opts, extra) = self.op.parse_args(args=args, lineno=self.lineno)
144 self._setToSelf(self.op, opts)
145 return self
146
147class F8_Bootloader(FC4_Bootloader):
148 removedKeywords = FC4_Bootloader.removedKeywords
149 removedAttrs = FC4_Bootloader.removedAttrs
150
151 def __init__(self, writePriority=10, *args, **kwargs):
152 FC4_Bootloader.__init__(self, writePriority, *args, **kwargs)
153
154 self.timeout = kwargs.get("timeout", None)
155 self.default = kwargs.get("default", "")
156
157 def _getArgsAsStr(self):
158 ret = FC4_Bootloader._getArgsAsStr(self)
159
160 if self.timeout is not None:
161 ret += " --timeout=%d" %(self.timeout,)
162 if self.default:
163 ret += " --default=%s" %(self.default,)
164
165 return ret
166
167 def _getParser(self):
168 op = FC4_Bootloader._getParser(self)
169 op.add_option("--timeout", dest="timeout", type="int")
170 op.add_option("--default", dest="default")
171 return op
172
173class F12_Bootloader(F8_Bootloader):
174 removedKeywords = F8_Bootloader.removedKeywords
175 removedAttrs = F8_Bootloader.removedAttrs
176
177 def _getParser(self):
178 op = F8_Bootloader._getParser(self)
179 op.add_option("--lba32", dest="forceLBA", deprecated=1, action="store_true")
180 return op
181
182class F14_Bootloader(F12_Bootloader):
183 removedKeywords = F12_Bootloader.removedKeywords + ["forceLBA"]
184 removedAttrs = F12_Bootloader.removedKeywords + ["forceLBA"]
185
186 def _getParser(self):
187 op = F12_Bootloader._getParser(self)
188 op.remove_option("--lba32")
189 return op
190
191class F15_Bootloader(F14_Bootloader):
192 removedKeywords = F14_Bootloader.removedKeywords
193 removedAttrs = F14_Bootloader.removedAttrs
194
195 def __init__(self, writePriority=10, *args, **kwargs):
196 F14_Bootloader.__init__(self, writePriority, *args, **kwargs)
197
198 self.isCrypted = kwargs.get("isCrypted", False)
199
200 def _getArgsAsStr(self):
201 ret = F14_Bootloader._getArgsAsStr(self)
202
203 if self.isCrypted:
204 ret += " --iscrypted"
205
206 return ret
207
208 def _getParser(self):
209 def password_cb(option, opt_str, value, parser):
210 parser.values.isCrypted = True
211 parser.values.password = value
212
213 op = F14_Bootloader._getParser(self)
214 op.add_option("--iscrypted", dest="isCrypted", action="store_true", default=False)
215 op.add_option("--md5pass", action="callback", callback=password_cb, nargs=1, type="string")
216 return op
diff --git a/scripts/lib/wic/3rdparty/pykickstart/commands/partition.py b/scripts/lib/wic/3rdparty/pykickstart/commands/partition.py
new file mode 100644
index 0000000000..56b91aa9d9
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/commands/partition.py
@@ -0,0 +1,314 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2005, 2006, 2007, 2008 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20from pykickstart.base import *
21from pykickstart.errors import *
22from pykickstart.options import *
23
24import gettext
25import warnings
26_ = lambda x: gettext.ldgettext("pykickstart", x)
27
28class FC3_PartData(BaseData):
29 removedKeywords = BaseData.removedKeywords
30 removedAttrs = BaseData.removedAttrs
31
32 def __init__(self, *args, **kwargs):
33 BaseData.__init__(self, *args, **kwargs)
34 self.active = kwargs.get("active", False)
35 self.primOnly = kwargs.get("primOnly", False)
36 self.end = kwargs.get("end", 0)
37 self.fstype = kwargs.get("fstype", "")
38 self.grow = kwargs.get("grow", False)
39 self.maxSizeMB = kwargs.get("maxSizeMB", 0)
40 self.format = kwargs.get("format", True)
41 self.onbiosdisk = kwargs.get("onbiosdisk", "")
42 self.disk = kwargs.get("disk", "")
43 self.onPart = kwargs.get("onPart", "")
44 self.recommended = kwargs.get("recommended", False)
45 self.size = kwargs.get("size", None)
46 self.start = kwargs.get("start", 0)
47 self.mountpoint = kwargs.get("mountpoint", "")
48
49 def __eq__(self, y):
50 if self.mountpoint:
51 return self.mountpoint == y.mountpoint
52 else:
53 return False
54
55 def _getArgsAsStr(self):
56 retval = ""
57
58 if self.active:
59 retval += " --active"
60 if self.primOnly:
61 retval += " --asprimary"
62 if hasattr(self, "end") and self.end != 0:
63 retval += " --end=%s" % self.end
64 if self.fstype != "":
65 retval += " --fstype=\"%s\"" % self.fstype
66 if self.grow:
67 retval += " --grow"
68 if self.maxSizeMB > 0:
69 retval += " --maxsize=%d" % self.maxSizeMB
70 if not self.format:
71 retval += " --noformat"
72 if self.onbiosdisk != "":
73 retval += " --onbiosdisk=%s" % self.onbiosdisk
74 if self.disk != "":
75 retval += " --ondisk=%s" % self.disk
76 if self.onPart != "":
77 retval += " --onpart=%s" % self.onPart
78 if self.recommended:
79 retval += " --recommended"
80 if self.size and self.size != 0:
81 retval += " --size=%s" % self.size
82 if hasattr(self, "start") and self.start != 0:
83 retval += " --start=%s" % self.start
84
85 return retval
86
87 def __str__(self):
88 retval = BaseData.__str__(self)
89 if self.mountpoint:
90 mountpoint_str = "%s" % self.mountpoint
91 else:
92 mountpoint_str = "(No mount point)"
93 retval += "part %s%s\n" % (mountpoint_str, self._getArgsAsStr())
94 return retval
95
96class FC4_PartData(FC3_PartData):
97 removedKeywords = FC3_PartData.removedKeywords
98 removedAttrs = FC3_PartData.removedAttrs
99
100 def __init__(self, *args, **kwargs):
101 FC3_PartData.__init__(self, *args, **kwargs)
102 self.bytesPerInode = kwargs.get("bytesPerInode", 4096)
103 self.fsopts = kwargs.get("fsopts", "")
104 self.label = kwargs.get("label", "")
105
106 def _getArgsAsStr(self):
107 retval = FC3_PartData._getArgsAsStr(self)
108
109 if hasattr(self, "bytesPerInode") and self.bytesPerInode != 0:
110 retval += " --bytes-per-inode=%d" % self.bytesPerInode
111 if self.fsopts != "":
112 retval += " --fsoptions=\"%s\"" % self.fsopts
113 if self.label != "":
114 retval += " --label=%s" % self.label
115
116 return retval
117
118class F9_PartData(FC4_PartData):
119 removedKeywords = FC4_PartData.removedKeywords + ["bytesPerInode"]
120 removedAttrs = FC4_PartData.removedAttrs + ["bytesPerInode"]
121
122 def __init__(self, *args, **kwargs):
123 FC4_PartData.__init__(self, *args, **kwargs)
124 self.deleteRemovedAttrs()
125
126 self.fsopts = kwargs.get("fsopts", "")
127 self.label = kwargs.get("label", "")
128 self.fsprofile = kwargs.get("fsprofile", "")
129 self.encrypted = kwargs.get("encrypted", False)
130 self.passphrase = kwargs.get("passphrase", "")
131
132 def _getArgsAsStr(self):
133 retval = FC4_PartData._getArgsAsStr(self)
134
135 if self.fsprofile != "":
136 retval += " --fsprofile=\"%s\"" % self.fsprofile
137 if self.encrypted:
138 retval += " --encrypted"
139
140 if self.passphrase != "":
141 retval += " --passphrase=\"%s\"" % self.passphrase
142
143 return retval
144
145class F11_PartData(F9_PartData):
146 removedKeywords = F9_PartData.removedKeywords + ["start", "end"]
147 removedAttrs = F9_PartData.removedAttrs + ["start", "end"]
148
149class F12_PartData(F11_PartData):
150 removedKeywords = F11_PartData.removedKeywords
151 removedAttrs = F11_PartData.removedAttrs
152
153 def __init__(self, *args, **kwargs):
154 F11_PartData.__init__(self, *args, **kwargs)
155
156 self.escrowcert = kwargs.get("escrowcert", "")
157 self.backuppassphrase = kwargs.get("backuppassphrase", False)
158
159 def _getArgsAsStr(self):
160 retval = F11_PartData._getArgsAsStr(self)
161
162 if self.encrypted and self.escrowcert != "":
163 retval += " --escrowcert=\"%s\"" % self.escrowcert
164
165 if self.backuppassphrase:
166 retval += " --backuppassphrase"
167
168 return retval
169
170F14_PartData = F12_PartData
171
172class FC3_Partition(KickstartCommand):
173 removedKeywords = KickstartCommand.removedKeywords
174 removedAttrs = KickstartCommand.removedAttrs
175
176 def __init__(self, writePriority=130, *args, **kwargs):
177 KickstartCommand.__init__(self, writePriority, *args, **kwargs)
178 self.op = self._getParser()
179
180 self.partitions = kwargs.get("partitions", [])
181
182 def __str__(self):
183 retval = ""
184
185 for part in self.partitions:
186 retval += part.__str__()
187
188 if retval != "":
189 return "# Disk partitioning information\n" + retval
190 else:
191 return ""
192
193 def _getParser(self):
194 def part_cb (option, opt_str, value, parser):
195 if value.startswith("/dev/"):
196 parser.values.ensure_value(option.dest, value[5:])
197 else:
198 parser.values.ensure_value(option.dest, value)
199
200 op = KSOptionParser()
201 op.add_option("--active", dest="active", action="store_true",
202 default=False)
203 op.add_option("--asprimary", dest="primOnly", action="store_true",
204 default=False)
205 op.add_option("--end", dest="end", action="store", type="int",
206 nargs=1)
207 op.add_option("--fstype", "--type", dest="fstype")
208 op.add_option("--grow", dest="grow", action="store_true", default=False)
209 op.add_option("--maxsize", dest="maxSizeMB", action="store", type="int",
210 nargs=1)
211 op.add_option("--noformat", dest="format", action="store_false",
212 default=True)
213 op.add_option("--onbiosdisk", dest="onbiosdisk")
214 op.add_option("--ondisk", "--ondrive", dest="disk")
215 op.add_option("--onpart", "--usepart", dest="onPart", action="callback",
216 callback=part_cb, nargs=1, type="string")
217 op.add_option("--recommended", dest="recommended", action="store_true",
218 default=False)
219 op.add_option("--size", dest="size", action="store", type="int",
220 nargs=1)
221 op.add_option("--start", dest="start", action="store", type="int",
222 nargs=1)
223 return op
224
225 def parse(self, args):
226 (opts, extra) = self.op.parse_args(args=args, lineno=self.lineno)
227
228 pd = self.handler.PartData()
229 self._setToObj(self.op, opts, pd)
230 pd.lineno = self.lineno
231 if extra:
232 pd.mountpoint = extra[0]
233 if pd in self.dataList():
234 warnings.warn(_("A partition with the mountpoint %s has already been defined.") % pd.mountpoint)
235 else:
236 pd.mountpoint = None
237
238 return pd
239
240 def dataList(self):
241 return self.partitions
242
243class FC4_Partition(FC3_Partition):
244 removedKeywords = FC3_Partition.removedKeywords
245 removedAttrs = FC3_Partition.removedAttrs
246
247 def __init__(self, writePriority=130, *args, **kwargs):
248 FC3_Partition.__init__(self, writePriority, *args, **kwargs)
249
250 def part_cb (option, opt_str, value, parser):
251 if value.startswith("/dev/"):
252 parser.values.ensure_value(option.dest, value[5:])
253 else:
254 parser.values.ensure_value(option.dest, value)
255
256 def _getParser(self):
257 op = FC3_Partition._getParser(self)
258 op.add_option("--bytes-per-inode", dest="bytesPerInode", action="store",
259 type="int", nargs=1)
260 op.add_option("--fsoptions", dest="fsopts")
261 op.add_option("--label", dest="label")
262 return op
263
264class F9_Partition(FC4_Partition):
265 removedKeywords = FC4_Partition.removedKeywords
266 removedAttrs = FC4_Partition.removedAttrs
267
268 def __init__(self, writePriority=130, *args, **kwargs):
269 FC4_Partition.__init__(self, writePriority, *args, **kwargs)
270
271 def part_cb (option, opt_str, value, parser):
272 if value.startswith("/dev/"):
273 parser.values.ensure_value(option.dest, value[5:])
274 else:
275 parser.values.ensure_value(option.dest, value)
276
277 def _getParser(self):
278 op = FC4_Partition._getParser(self)
279 op.add_option("--bytes-per-inode", deprecated=1)
280 op.add_option("--fsprofile")
281 op.add_option("--encrypted", action="store_true", default=False)
282 op.add_option("--passphrase")
283 return op
284
285class F11_Partition(F9_Partition):
286 removedKeywords = F9_Partition.removedKeywords
287 removedAttrs = F9_Partition.removedAttrs
288
289 def _getParser(self):
290 op = F9_Partition._getParser(self)
291 op.add_option("--start", deprecated=1)
292 op.add_option("--end", deprecated=1)
293 return op
294
295class F12_Partition(F11_Partition):
296 removedKeywords = F11_Partition.removedKeywords
297 removedAttrs = F11_Partition.removedAttrs
298
299 def _getParser(self):
300 op = F11_Partition._getParser(self)
301 op.add_option("--escrowcert")
302 op.add_option("--backuppassphrase", action="store_true", default=False)
303 return op
304
305class F14_Partition(F12_Partition):
306 removedKeywords = F12_Partition.removedKeywords
307 removedAttrs = F12_Partition.removedAttrs
308
309 def _getParser(self):
310 op = F12_Partition._getParser(self)
311 op.remove_option("--bytes-per-inode")
312 op.remove_option("--start")
313 op.remove_option("--end")
314 return op
diff --git a/scripts/lib/wic/3rdparty/pykickstart/constants.py b/scripts/lib/wic/3rdparty/pykickstart/constants.py
new file mode 100644
index 0000000000..5e12fc80ec
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/constants.py
@@ -0,0 +1,57 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2005-2007 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20CLEARPART_TYPE_LINUX = 0
21CLEARPART_TYPE_ALL = 1
22CLEARPART_TYPE_NONE = 2
23
24DISPLAY_MODE_CMDLINE = 0
25DISPLAY_MODE_GRAPHICAL = 1
26DISPLAY_MODE_TEXT = 2
27
28FIRSTBOOT_DEFAULT = 0
29FIRSTBOOT_SKIP = 1
30FIRSTBOOT_RECONFIG = 2
31
32KS_MISSING_PROMPT = 0
33KS_MISSING_IGNORE = 1
34
35SELINUX_DISABLED = 0
36SELINUX_ENFORCING = 1
37SELINUX_PERMISSIVE = 2
38
39KS_SCRIPT_PRE = 0
40KS_SCRIPT_POST = 1
41KS_SCRIPT_TRACEBACK = 2
42
43KS_WAIT = 0
44KS_REBOOT = 1
45KS_SHUTDOWN = 2
46
47KS_INSTKEY_SKIP = -99
48
49BOOTPROTO_DHCP = "dhcp"
50BOOTPROTO_BOOTP = "bootp"
51BOOTPROTO_STATIC = "static"
52BOOTPROTO_QUERY = "query"
53BOOTPROTO_IBFT = "ibft"
54
55GROUP_REQUIRED = 0
56GROUP_DEFAULT = 1
57GROUP_ALL = 2
diff --git a/scripts/lib/wic/3rdparty/pykickstart/errors.py b/scripts/lib/wic/3rdparty/pykickstart/errors.py
new file mode 100644
index 0000000000..a234d99d43
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/errors.py
@@ -0,0 +1,103 @@
1#
2# errors.py: Kickstart error handling.
3#
4# Chris Lumens <clumens@redhat.com>
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20"""
21Error handling classes and functions.
22
23This module exports a single function:
24
25 formatErrorMsg - Properly formats an error message.
26
27It also exports several exception classes:
28
29 KickstartError - A generic exception class.
30
31 KickstartParseError - An exception for errors relating to parsing.
32
33 KickstartValueError - An exception for errors relating to option
34 processing.
35
36 KickstartVersionError - An exception for errors relating to unsupported
37 syntax versions.
38"""
39import gettext
40_ = lambda x: gettext.ldgettext("pykickstart", x)
41
42def formatErrorMsg(lineno, msg=""):
43 """Properly format the error message msg for inclusion in an exception."""
44 if msg != "":
45 mapping = {"lineno": lineno, "msg": msg}
46 return _("The following problem occurred on line %(lineno)s of the kickstart file:\n\n%(msg)s\n") % mapping
47 else:
48 return _("There was a problem reading from line %s of the kickstart file") % lineno
49
50class KickstartError(Exception):
51 """A generic exception class for unspecific error conditions."""
52 def __init__(self, val = ""):
53 """Create a new KickstartError exception instance with the descriptive
54 message val. val should be the return value of formatErrorMsg.
55 """
56 Exception.__init__(self)
57 self.value = val
58
59 def __str__ (self):
60 return self.value
61
62class KickstartParseError(KickstartError):
63 """An exception class for errors when processing the input file, such as
64 unknown options, commands, or sections.
65 """
66 def __init__(self, msg):
67 """Create a new KickstartParseError exception instance with the
68 descriptive message val. val should be the return value of
69 formatErrorMsg.
70 """
71 KickstartError.__init__(self, msg)
72
73 def __str__(self):
74 return self.value
75
76class KickstartValueError(KickstartError):
77 """An exception class for errors when processing arguments to commands,
78 such as too many arguments, too few arguments, or missing required
79 arguments.
80 """
81 def __init__(self, msg):
82 """Create a new KickstartValueError exception instance with the
83 descriptive message val. val should be the return value of
84 formatErrorMsg.
85 """
86 KickstartError.__init__(self, msg)
87
88 def __str__ (self):
89 return self.value
90
91class KickstartVersionError(KickstartError):
92 """An exception class for errors related to using an incorrect version of
93 kickstart syntax.
94 """
95 def __init__(self, msg):
96 """Create a new KickstartVersionError exception instance with the
97 descriptive message val. val should be the return value of
98 formatErrorMsg.
99 """
100 KickstartError.__init__(self, msg)
101
102 def __str__ (self):
103 return self.value
diff --git a/scripts/lib/wic/3rdparty/pykickstart/handlers/__init__.py b/scripts/lib/wic/3rdparty/pykickstart/handlers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/handlers/__init__.py
diff --git a/scripts/lib/wic/3rdparty/pykickstart/handlers/control.py b/scripts/lib/wic/3rdparty/pykickstart/handlers/control.py
new file mode 100644
index 0000000000..8dc80d1ebe
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/handlers/control.py
@@ -0,0 +1,46 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2007, 2008, 2009, 2010 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20from pykickstart.version import *
21from pykickstart.commands import *
22
23# This map is keyed on kickstart syntax version as provided by
24# pykickstart.version. Within each sub-dict is a mapping from command name
25# to the class that handles it. This is an onto mapping - that is, multiple
26# command names can map to the same class. However, the Handler will ensure
27# that only one instance of each class ever exists.
28commandMap = {
29 # based on f15
30 F16: {
31 "bootloader": bootloader.F15_Bootloader,
32 "part": partition.F14_Partition,
33 "partition": partition.F14_Partition,
34 },
35}
36
37# This map is keyed on kickstart syntax version as provided by
38# pykickstart.version. Within each sub-dict is a mapping from a data object
39# name to the class that provides it. This is a bijective mapping - that is,
40# each name maps to exactly one data class and all data classes have a name.
41# More than one instance of each class is allowed to exist, however.
42dataMap = {
43 F16: {
44 "PartData": partition.F14_PartData,
45 },
46}
diff --git a/scripts/lib/wic/3rdparty/pykickstart/handlers/f16.py b/scripts/lib/wic/3rdparty/pykickstart/handlers/f16.py
new file mode 100644
index 0000000000..3c52f8d754
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/handlers/f16.py
@@ -0,0 +1,24 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2011 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20from pykickstart.base import *
21from pykickstart.version import *
22
23class F16Handler(BaseHandler):
24 version = F16
diff --git a/scripts/lib/wic/3rdparty/pykickstart/ko.py b/scripts/lib/wic/3rdparty/pykickstart/ko.py
new file mode 100644
index 0000000000..1350d19c70
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/ko.py
@@ -0,0 +1,37 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2009 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20"""
21Base classes for internal pykickstart use.
22
23The module exports the following important classes:
24
25 KickstartObject - The base class for all classes in pykickstart
26"""
27
28class KickstartObject(object):
29 """The base class for all other classes in pykickstart."""
30 def __init__(self, *args, **kwargs):
31 """Create a new KickstartObject instance. All other classes in
32 pykickstart should be derived from this one. Instance attributes:
33 """
34 pass
35
36 def __str__(self):
37 return ""
diff --git a/scripts/lib/wic/3rdparty/pykickstart/options.py b/scripts/lib/wic/3rdparty/pykickstart/options.py
new file mode 100644
index 0000000000..341c5d7298
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/options.py
@@ -0,0 +1,204 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2005, 2006, 2007 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20"""
21Specialized option handling.
22
23This module exports two classes:
24
25 KSOptionParser - A specialized subclass of OptionParser to be used
26 in BaseHandler subclasses.
27
28 KSOption - A specialized subclass of Option.
29"""
30import warnings
31from copy import copy
32from optparse import *
33
34from constants import *
35from errors import *
36from version import *
37
38import gettext
39_ = lambda x: gettext.ldgettext("pykickstart", x)
40
41class KSOptionParser(OptionParser):
42 """A specialized subclass of optparse.OptionParser to handle extra option
43 attribute checking, work error reporting into the KickstartParseError
44 framework, and to turn off the default help.
45 """
46 def exit(self, status=0, msg=None):
47 pass
48
49 def error(self, msg):
50 if self.lineno != None:
51 raise KickstartParseError, formatErrorMsg(self.lineno, msg=msg)
52 else:
53 raise KickstartParseError, msg
54
55 def keys(self):
56 retval = []
57
58 for opt in self.option_list:
59 if opt not in retval:
60 retval.append(opt.dest)
61
62 return retval
63
64 def _init_parsing_state (self):
65 OptionParser._init_parsing_state(self)
66 self.option_seen = {}
67
68 def check_values (self, values, args):
69 def seen(self, option):
70 return self.option_seen.has_key(option)
71
72 def usedTooNew(self, option):
73 return option.introduced and option.introduced > self.version
74
75 def usedDeprecated(self, option):
76 return option.deprecated
77
78 def usedRemoved(self, option):
79 return option.removed and option.removed <= self.version
80
81 for option in filter(lambda o: isinstance(o, Option), self.option_list):
82 if option.required and not seen(self, option):
83 raise KickstartValueError, formatErrorMsg(self.lineno, _("Option %s is required") % option)
84 elif seen(self, option) and usedTooNew(self, option):
85 mapping = {"option": option, "intro": versionToString(option.introduced),
86 "version": versionToString(self.version)}
87 self.error(_("The %(option)s option was introduced in version %(intro)s, but you are using kickstart syntax version %(version)s.") % mapping)
88 elif seen(self, option) and usedRemoved(self, option):
89 mapping = {"option": option, "removed": versionToString(option.removed),
90 "version": versionToString(self.version)}
91
92 if option.removed == self.version:
93 self.error(_("The %(option)s option is no longer supported.") % mapping)
94 else:
95 self.error(_("The %(option)s option was removed in version %(removed)s, but you are using kickstart syntax version %(version)s.") % mapping)
96 elif seen(self, option) and usedDeprecated(self, option):
97 mapping = {"lineno": self.lineno, "option": option}
98 warnings.warn(_("Ignoring deprecated option on line %(lineno)s: The %(option)s option has been deprecated and no longer has any effect. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to remove this option.") % mapping, DeprecationWarning)
99
100 return (values, args)
101
102 def parse_args(self, *args, **kwargs):
103 if kwargs.has_key("lineno"):
104 self.lineno = kwargs.pop("lineno")
105
106 return OptionParser.parse_args(self, **kwargs)
107
108 def __init__(self, mapping=None, version=None):
109 """Create a new KSOptionParser instance. Each KickstartCommand
110 subclass should create one instance of KSOptionParser, providing
111 at least the lineno attribute. mapping and version are not required.
112 Instance attributes:
113
114 mapping -- A mapping from option strings to different values.
115 version -- The version of the kickstart syntax we are checking
116 against.
117 """
118 OptionParser.__init__(self, option_class=KSOption,
119 add_help_option=False,
120 conflict_handler="resolve")
121 if mapping is None:
122 self.map = {}
123 else:
124 self.map = mapping
125
126 self.lineno = None
127 self.option_seen = {}
128 self.version = version
129
130def _check_ksboolean(option, opt, value):
131 if value.lower() in ("on", "yes", "true", "1"):
132 return True
133 elif value.lower() in ("off", "no", "false", "0"):
134 return False
135 else:
136 mapping = {"opt": opt, "value": value}
137 raise OptionValueError(_("Option %(opt)s: invalid boolean value: %(value)r") % mapping)
138
139def _check_string(option, opt, value):
140 if len(value) > 2 and value.startswith("--"):
141 mapping = {"opt": opt, "value": value}
142 raise OptionValueError(_("Option %(opt)s: invalid string value: %(value)r") % mapping)
143 else:
144 return value
145
146# Creates a new Option class that supports several new attributes:
147# - required: any option with this attribute must be supplied or an exception
148# is thrown
149# - introduced: the kickstart syntax version that this option first appeared
150# in - an exception will be raised if the option is used and
151# the specified syntax version is less than the value of this
152# attribute
153# - deprecated: the kickstart syntax version that this option was deprecated
154# in - a DeprecationWarning will be thrown if the option is
155# used and the specified syntax version is greater than the
156# value of this attribute
157# - removed: the kickstart syntax version that this option was removed in - an
158# exception will be raised if the option is used and the specified
159# syntax version is greated than the value of this attribute
160# Also creates a new type:
161# - ksboolean: support various kinds of boolean values on an option
162# And two new actions:
163# - map : allows you to define an opt -> val mapping such that dest gets val
164# when opt is seen
165# - map_extend: allows you to define an opt -> [val1, ... valn] mapping such
166# that dest gets a list of vals built up when opt is seen
167class KSOption (Option):
168 ATTRS = Option.ATTRS + ['introduced', 'deprecated', 'removed', 'required']
169 ACTIONS = Option.ACTIONS + ("map", "map_extend",)
170 STORE_ACTIONS = Option.STORE_ACTIONS + ("map", "map_extend",)
171
172 TYPES = Option.TYPES + ("ksboolean", "string")
173 TYPE_CHECKER = copy(Option.TYPE_CHECKER)
174 TYPE_CHECKER["ksboolean"] = _check_ksboolean
175 TYPE_CHECKER["string"] = _check_string
176
177 def _check_required(self):
178 if self.required and not self.takes_value():
179 raise OptionError(_("Required flag set for option that doesn't take a value"), self)
180
181 # Make sure _check_required() is called from the constructor!
182 CHECK_METHODS = Option.CHECK_METHODS + [_check_required]
183
184 def process (self, opt, value, values, parser):
185 Option.process(self, opt, value, values, parser)
186 parser.option_seen[self] = 1
187
188 # Override default take_action method to handle our custom actions.
189 def take_action(self, action, dest, opt, value, values, parser):
190 if action == "map":
191 values.ensure_value(dest, parser.map[opt.lstrip('-')])
192 elif action == "map_extend":
193 values.ensure_value(dest, []).extend(parser.map[opt.lstrip('-')])
194 else:
195 Option.take_action(self, action, dest, opt, value, values, parser)
196
197 def takes_value(self):
198 # Deprecated options don't take a value.
199 return Option.takes_value(self) and not self.deprecated
200
201 def __init__(self, *args, **kwargs):
202 self.deprecated = False
203 self.required = False
204 Option.__init__(self, *args, **kwargs)
diff --git a/scripts/lib/wic/3rdparty/pykickstart/parser.py b/scripts/lib/wic/3rdparty/pykickstart/parser.py
new file mode 100644
index 0000000000..9c9674bf73
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/parser.py
@@ -0,0 +1,619 @@
1#
2# parser.py: Kickstart file parser.
3#
4# Chris Lumens <clumens@redhat.com>
5#
6# Copyright 2005, 2006, 2007, 2008, 2011 Red Hat, Inc.
7#
8# This copyrighted material is made available to anyone wishing to use, modify,
9# copy, or redistribute it subject to the terms and conditions of the GNU
10# General Public License v.2. This program is distributed in the hope that it
11# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
12# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13# See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc., 51
17# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
18# trademarks that are incorporated in the source code or documentation are not
19# subject to the GNU General Public License and may only be used or replicated
20# with the express permission of Red Hat, Inc.
21#
22"""
23Main kickstart file processing module.
24
25This module exports several important classes:
26
27 Script - Representation of a single %pre, %post, or %traceback script.
28
29 Packages - Representation of the %packages section.
30
31 KickstartParser - The kickstart file parser state machine.
32"""
33
34from collections import Iterator
35import os
36import shlex
37import sys
38import tempfile
39from copy import copy
40from optparse import *
41
42import constants
43from errors import KickstartError, KickstartParseError, KickstartValueError, formatErrorMsg
44from ko import KickstartObject
45from sections import *
46import version
47
48import gettext
49_ = lambda x: gettext.ldgettext("pykickstart", x)
50
51STATE_END = "end"
52STATE_COMMANDS = "commands"
53
54ver = version.DEVEL
55
56
57class PutBackIterator(Iterator):
58 def __init__(self, iterable):
59 self._iterable = iter(iterable)
60 self._buf = None
61
62 def __iter__(self):
63 return self
64
65 def put(self, s):
66 self._buf = s
67
68 def next(self):
69 if self._buf:
70 retval = self._buf
71 self._buf = None
72 return retval
73 else:
74 return self._iterable.next()
75
76###
77### SCRIPT HANDLING
78###
79class Script(KickstartObject):
80 """A class representing a single kickstart script. If functionality beyond
81 just a data representation is needed (for example, a run method in
82 anaconda), Script may be subclassed. Although a run method is not
83 provided, most of the attributes of Script have to do with running the
84 script. Instances of Script are held in a list by the Version object.
85 """
86 def __init__(self, script, *args , **kwargs):
87 """Create a new Script instance. Instance attributes:
88
89 errorOnFail -- If execution of the script fails, should anaconda
90 stop, display an error, and then reboot without
91 running any other scripts?
92 inChroot -- Does the script execute in anaconda's chroot
93 environment or not?
94 interp -- The program that should be used to interpret this
95 script.
96 lineno -- The line number this script starts on.
97 logfile -- Where all messages from the script should be logged.
98 script -- A string containing all the lines of the script.
99 type -- The type of the script, which can be KS_SCRIPT_* from
100 pykickstart.constants.
101 """
102 KickstartObject.__init__(self, *args, **kwargs)
103 self.script = "".join(script)
104
105 self.interp = kwargs.get("interp", "/bin/sh")
106 self.inChroot = kwargs.get("inChroot", False)
107 self.lineno = kwargs.get("lineno", None)
108 self.logfile = kwargs.get("logfile", None)
109 self.errorOnFail = kwargs.get("errorOnFail", False)
110 self.type = kwargs.get("type", constants.KS_SCRIPT_PRE)
111
112 def __str__(self):
113 """Return a string formatted for output to a kickstart file."""
114 retval = ""
115
116 if self.type == constants.KS_SCRIPT_PRE:
117 retval += '\n%pre'
118 elif self.type == constants.KS_SCRIPT_POST:
119 retval += '\n%post'
120 elif self.type == constants.KS_SCRIPT_TRACEBACK:
121 retval += '\n%traceback'
122
123 if self.interp != "/bin/sh" and self.interp != "":
124 retval += " --interpreter=%s" % self.interp
125 if self.type == constants.KS_SCRIPT_POST and not self.inChroot:
126 retval += " --nochroot"
127 if self.logfile != None:
128 retval += " --logfile %s" % self.logfile
129 if self.errorOnFail:
130 retval += " --erroronfail"
131
132 if self.script.endswith("\n"):
133 if ver >= version.F8:
134 return retval + "\n%s%%end\n" % self.script
135 else:
136 return retval + "\n%s\n" % self.script
137 else:
138 if ver >= version.F8:
139 return retval + "\n%s\n%%end\n" % self.script
140 else:
141 return retval + "\n%s\n" % self.script
142
143
144##
145## PACKAGE HANDLING
146##
147class Group:
148 """A class representing a single group in the %packages section."""
149 def __init__(self, name="", include=constants.GROUP_DEFAULT):
150 """Create a new Group instance. Instance attributes:
151
152 name -- The group's identifier
153 include -- The level of how much of the group should be included.
154 Values can be GROUP_* from pykickstart.constants.
155 """
156 self.name = name
157 self.include = include
158
159 def __str__(self):
160 """Return a string formatted for output to a kickstart file."""
161 if self.include == constants.GROUP_REQUIRED:
162 return "@%s --nodefaults" % self.name
163 elif self.include == constants.GROUP_ALL:
164 return "@%s --optional" % self.name
165 else:
166 return "@%s" % self.name
167
168 def __cmp__(self, other):
169 if self.name < other.name:
170 return -1
171 elif self.name > other.name:
172 return 1
173 return 0
174
175class Packages(KickstartObject):
176 """A class representing the %packages section of the kickstart file."""
177 def __init__(self, *args, **kwargs):
178 """Create a new Packages instance. Instance attributes:
179
180 addBase -- Should the Base group be installed even if it is
181 not specified?
182 default -- Should the default package set be selected?
183 excludedList -- A list of all the packages marked for exclusion in
184 the %packages section, without the leading minus
185 symbol.
186 excludeDocs -- Should documentation in each package be excluded?
187 groupList -- A list of Group objects representing all the groups
188 specified in the %packages section. Names will be
189 stripped of the leading @ symbol.
190 excludedGroupList -- A list of Group objects representing all the
191 groups specified for removal in the %packages
192 section. Names will be stripped of the leading
193 -@ symbols.
194 handleMissing -- If unknown packages are specified in the %packages
195 section, should it be ignored or not? Values can
196 be KS_MISSING_* from pykickstart.constants.
197 packageList -- A list of all the packages specified in the
198 %packages section.
199 instLangs -- A list of languages to install.
200 """
201 KickstartObject.__init__(self, *args, **kwargs)
202
203 self.addBase = True
204 self.default = False
205 self.excludedList = []
206 self.excludedGroupList = []
207 self.excludeDocs = False
208 self.groupList = []
209 self.handleMissing = constants.KS_MISSING_PROMPT
210 self.packageList = []
211 self.instLangs = None
212
213 def __str__(self):
214 """Return a string formatted for output to a kickstart file."""
215 pkgs = ""
216
217 if not self.default:
218 grps = self.groupList
219 grps.sort()
220 for grp in grps:
221 pkgs += "%s\n" % grp.__str__()
222
223 p = self.packageList
224 p.sort()
225 for pkg in p:
226 pkgs += "%s\n" % pkg
227
228 grps = self.excludedGroupList
229 grps.sort()
230 for grp in grps:
231 pkgs += "-%s\n" % grp.__str__()
232
233 p = self.excludedList
234 p.sort()
235 for pkg in p:
236 pkgs += "-%s\n" % pkg
237
238 if pkgs == "":
239 return ""
240
241 retval = "\n%packages"
242
243 if self.default:
244 retval += " --default"
245 if self.excludeDocs:
246 retval += " --excludedocs"
247 if not self.addBase:
248 retval += " --nobase"
249 if self.handleMissing == constants.KS_MISSING_IGNORE:
250 retval += " --ignoremissing"
251 if self.instLangs:
252 retval += " --instLangs=%s" % self.instLangs
253
254 if ver >= version.F8:
255 return retval + "\n" + pkgs + "\n%end\n"
256 else:
257 return retval + "\n" + pkgs + "\n"
258
259 def _processGroup (self, line):
260 op = OptionParser()
261 op.add_option("--nodefaults", action="store_true", default=False)
262 op.add_option("--optional", action="store_true", default=False)
263
264 (opts, extra) = op.parse_args(args=line.split())
265
266 if opts.nodefaults and opts.optional:
267 raise KickstartValueError, _("Group cannot specify both --nodefaults and --optional")
268
269 # If the group name has spaces in it, we have to put it back together
270 # now.
271 grp = " ".join(extra)
272
273 if opts.nodefaults:
274 self.groupList.append(Group(name=grp, include=constants.GROUP_REQUIRED))
275 elif opts.optional:
276 self.groupList.append(Group(name=grp, include=constants.GROUP_ALL))
277 else:
278 self.groupList.append(Group(name=grp, include=constants.GROUP_DEFAULT))
279
280 def add (self, pkgList):
281 """Given a list of lines from the input file, strip off any leading
282 symbols and add the result to the appropriate list.
283 """
284 existingExcludedSet = set(self.excludedList)
285 existingPackageSet = set(self.packageList)
286 newExcludedSet = set()
287 newPackageSet = set()
288
289 excludedGroupList = []
290
291 for pkg in pkgList:
292 stripped = pkg.strip()
293
294 if stripped[0] == "@":
295 self._processGroup(stripped[1:])
296 elif stripped[0] == "-":
297 if stripped[1] == "@":
298 excludedGroupList.append(Group(name=stripped[2:]))
299 else:
300 newExcludedSet.add(stripped[1:])
301 else:
302 newPackageSet.add(stripped)
303
304 # Groups have to be excluded in two different ways (note: can't use
305 # sets here because we have to store objects):
306 excludedGroupNames = map(lambda g: g.name, excludedGroupList)
307
308 # First, an excluded group may be cancelling out a previously given
309 # one. This is often the case when using %include. So there we should
310 # just remove the group from the list.
311 self.groupList = filter(lambda g: g.name not in excludedGroupNames, self.groupList)
312
313 # Second, the package list could have included globs which are not
314 # processed by pykickstart. In that case we need to preserve a list of
315 # excluded groups so whatever tool doing package/group installation can
316 # take appropriate action.
317 self.excludedGroupList.extend(excludedGroupList)
318
319 existingPackageSet = (existingPackageSet - newExcludedSet) | newPackageSet
320 existingExcludedSet = (existingExcludedSet - existingPackageSet) | newExcludedSet
321
322 self.packageList = list(existingPackageSet)
323 self.excludedList = list(existingExcludedSet)
324
325
326###
327### PARSER
328###
329class KickstartParser:
330 """The kickstart file parser class as represented by a basic state
331 machine. To create a specialized parser, make a subclass and override
332 any of the methods you care about. Methods that don't need to do
333 anything may just pass. However, _stateMachine should never be
334 overridden.
335 """
336 def __init__ (self, handler, followIncludes=True, errorsAreFatal=True,
337 missingIncludeIsFatal=True):
338 """Create a new KickstartParser instance. Instance attributes:
339
340 errorsAreFatal -- Should errors cause processing to halt, or
341 just print a message to the screen? This
342 is most useful for writing syntax checkers
343 that may want to continue after an error is
344 encountered.
345 followIncludes -- If %include is seen, should the included
346 file be checked as well or skipped?
347 handler -- An instance of a BaseHandler subclass. If
348 None, the input file will still be parsed
349 but no data will be saved and no commands
350 will be executed.
351 missingIncludeIsFatal -- Should missing include files be fatal, even
352 if errorsAreFatal is False?
353 """
354 self.errorsAreFatal = errorsAreFatal
355 self.followIncludes = followIncludes
356 self.handler = handler
357 self.currentdir = {}
358 self.missingIncludeIsFatal = missingIncludeIsFatal
359
360 self._state = STATE_COMMANDS
361 self._includeDepth = 0
362 self._line = ""
363
364 self.version = self.handler.version
365
366 global ver
367 ver = self.version
368
369 self._sections = {}
370 self.setupSections()
371
372 def _reset(self):
373 """Reset the internal variables of the state machine for a new kickstart file."""
374 self._state = STATE_COMMANDS
375 self._includeDepth = 0
376
377 def getSection(self, s):
378 """Return a reference to the requested section (s must start with '%'s),
379 or raise KeyError if not found.
380 """
381 return self._sections[s]
382
383 def handleCommand (self, lineno, args):
384 """Given the list of command and arguments, call the Version's
385 dispatcher method to handle the command. Returns the command or
386 data object returned by the dispatcher. This method may be
387 overridden in a subclass if necessary.
388 """
389 if self.handler:
390 self.handler.currentCmd = args[0]
391 self.handler.currentLine = self._line
392 retval = self.handler.dispatcher(args, lineno)
393
394 return retval
395
396 def registerSection(self, obj):
397 """Given an instance of a Section subclass, register the new section
398 with the parser. Calling this method means the parser will
399 recognize your new section and dispatch into the given object to
400 handle it.
401 """
402 if not obj.sectionOpen:
403 raise TypeError, "no sectionOpen given for section %s" % obj
404
405 if not obj.sectionOpen.startswith("%"):
406 raise TypeError, "section %s tag does not start with a %%" % obj.sectionOpen
407
408 self._sections[obj.sectionOpen] = obj
409
410 def _finalize(self, obj):
411 """Called at the close of a kickstart section to take any required
412 actions. Internally, this is used to add scripts once we have the
413 whole body read.
414 """
415 obj.finalize()
416 self._state = STATE_COMMANDS
417
418 def _handleSpecialComments(self, line):
419 """Kickstart recognizes a couple special comments."""
420 if self._state != STATE_COMMANDS:
421 return
422
423 # Save the platform for s-c-kickstart.
424 if line[:10] == "#platform=":
425 self.handler.platform = self._line[11:]
426
427 def _readSection(self, lineIter, lineno):
428 obj = self._sections[self._state]
429
430 while True:
431 try:
432 line = lineIter.next()
433 if line == "":
434 # This section ends at the end of the file.
435 if self.version >= version.F8:
436 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end."))
437
438 self._finalize(obj)
439 except StopIteration:
440 break
441
442 lineno += 1
443
444 # Throw away blank lines and comments, unless the section wants all
445 # lines.
446 if self._isBlankOrComment(line) and not obj.allLines:
447 continue
448
449 if line.startswith("%"):
450 args = shlex.split(line)
451
452 if args and args[0] == "%end":
453 # This is a properly terminated section.
454 self._finalize(obj)
455 break
456 elif args and args[0] == "%ksappend":
457 continue
458 elif args and (self._validState(args[0]) or args[0] in ["%include", "%ksappend"]):
459 # This is an unterminated section.
460 if self.version >= version.F8:
461 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Section does not end with %%end."))
462
463 # Finish up. We do not process the header here because
464 # kicking back out to STATE_COMMANDS will ensure that happens.
465 lineIter.put(line)
466 lineno -= 1
467 self._finalize(obj)
468 break
469 else:
470 # This is just a line within a section. Pass it off to whatever
471 # section handles it.
472 obj.handleLine(line)
473
474 return lineno
475
476 def _validState(self, st):
477 """Is the given section tag one that has been registered with the parser?"""
478 return st in self._sections.keys()
479
480 def _tryFunc(self, fn):
481 """Call the provided function (which doesn't take any arguments) and
482 do the appropriate error handling. If errorsAreFatal is False, this
483 function will just print the exception and keep going.
484 """
485 try:
486 fn()
487 except Exception, msg:
488 if self.errorsAreFatal:
489 raise
490 else:
491 print msg
492
493 def _isBlankOrComment(self, line):
494 return line.isspace() or line == "" or line.lstrip()[0] == '#'
495
496 def _stateMachine(self, lineIter):
497 # For error reporting.
498 lineno = 0
499
500 while True:
501 # Get the next line out of the file, quitting if this is the last line.
502 try:
503 self._line = lineIter.next()
504 if self._line == "":
505 break
506 except StopIteration:
507 break
508
509 lineno += 1
510
511 # Eliminate blank lines, whitespace-only lines, and comments.
512 if self._isBlankOrComment(self._line):
513 self._handleSpecialComments(self._line)
514 continue
515
516 # Remove any end-of-line comments.
517 sanitized = self._line.split("#")[0]
518
519 # Then split the line.
520 args = shlex.split(sanitized.rstrip())
521
522 if args[0] == "%include":
523 # This case comes up primarily in ksvalidator.
524 if not self.followIncludes:
525 continue
526
527 if len(args) == 1 or not args[1]:
528 raise KickstartParseError, formatErrorMsg(lineno)
529
530 self._includeDepth += 1
531
532 try:
533 self.readKickstart(args[1], reset=False)
534 except KickstartError:
535 # Handle the include file being provided over the
536 # network in a %pre script. This case comes up in the
537 # early parsing in anaconda.
538 if self.missingIncludeIsFatal:
539 raise
540
541 self._includeDepth -= 1
542 continue
543
544 # Now on to the main event.
545 if self._state == STATE_COMMANDS:
546 if args[0] == "%ksappend":
547 # This is handled by the preprocess* functions, so continue.
548 continue
549 elif args[0][0] == '%':
550 # This is the beginning of a new section. Handle its header
551 # here.
552 newSection = args[0]
553 if not self._validState(newSection):
554 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown kickstart section: %s" % newSection))
555
556 self._state = newSection
557 obj = self._sections[self._state]
558 self._tryFunc(lambda: obj.handleHeader(lineno, args))
559
560 # This will handle all section processing, kicking us back
561 # out to STATE_COMMANDS at the end with the current line
562 # being the next section header, etc.
563 lineno = self._readSection(lineIter, lineno)
564 else:
565 # This is a command in the command section. Dispatch to it.
566 self._tryFunc(lambda: self.handleCommand(lineno, args))
567 elif self._state == STATE_END:
568 break
569
570 def readKickstartFromString (self, s, reset=True):
571 """Process a kickstart file, provided as the string str."""
572 if reset:
573 self._reset()
574
575 # Add a "" to the end of the list so the string reader acts like the
576 # file reader and we only get StopIteration when we're after the final
577 # line of input.
578 i = PutBackIterator(s.splitlines(True) + [""])
579 self._stateMachine (i)
580
581 def readKickstart(self, f, reset=True):
582 """Process a kickstart file, given by the filename f."""
583 if reset:
584 self._reset()
585
586 # an %include might not specify a full path. if we don't try to figure
587 # out what the path should have been, then we're unable to find it
588 # requiring full path specification, though, sucks. so let's make
589 # the reading "smart" by keeping track of what the path is at each
590 # include depth.
591 if not os.path.exists(f):
592 if self.currentdir.has_key(self._includeDepth - 1):
593 if os.path.exists(os.path.join(self.currentdir[self._includeDepth - 1], f)):
594 f = os.path.join(self.currentdir[self._includeDepth - 1], f)
595
596 cd = os.path.dirname(f)
597 if not cd.startswith("/"):
598 cd = os.path.abspath(cd)
599 self.currentdir[self._includeDepth] = cd
600
601 try:
602 s = file(f).read()
603 except IOError, e:
604 raise KickstartError, formatErrorMsg(0, msg=_("Unable to open input kickstart file: %s") % e.strerror)
605
606 self.readKickstartFromString(s, reset=False)
607
608 def setupSections(self):
609 """Install the sections all kickstart files support. You may override
610 this method in a subclass, but should avoid doing so unless you know
611 what you're doing.
612 """
613 self._sections = {}
614
615 # Install the sections all kickstart files support.
616 self.registerSection(PreScriptSection(self.handler, dataObj=Script))
617 self.registerSection(PostScriptSection(self.handler, dataObj=Script))
618 self.registerSection(TracebackScriptSection(self.handler, dataObj=Script))
619 self.registerSection(PackageSection(self.handler))
diff --git a/scripts/lib/wic/3rdparty/pykickstart/sections.py b/scripts/lib/wic/3rdparty/pykickstart/sections.py
new file mode 100644
index 0000000000..44df856b8d
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/sections.py
@@ -0,0 +1,244 @@
1#
2# sections.py: Kickstart file sections.
3#
4# Chris Lumens <clumens@redhat.com>
5#
6# Copyright 2011 Red Hat, Inc.
7#
8# This copyrighted material is made available to anyone wishing to use, modify,
9# copy, or redistribute it subject to the terms and conditions of the GNU
10# General Public License v.2. This program is distributed in the hope that it
11# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
12# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13# See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc., 51
17# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
18# trademarks that are incorporated in the source code or documentation are not
19# subject to the GNU General Public License and may only be used or replicated
20# with the express permission of Red Hat, Inc.
21#
22"""
23This module exports the classes that define a section of a kickstart file. A
24section is a chunk of the file starting with a %tag and ending with a %end.
25Examples of sections include %packages, %pre, and %post.
26
27You may use this module to define your own custom sections which will be
28treated just the same as a predefined one by the kickstart parser. All that
29is necessary is to create a new subclass of Section and call
30parser.registerSection with an instance of your new class.
31"""
32from constants import *
33from options import KSOptionParser
34from version import *
35
36class Section(object):
37 """The base class for defining kickstart sections. You are free to
38 subclass this as appropriate.
39
40 Class attributes:
41
42 allLines -- Does this section require the parser to call handleLine
43 for every line in the section, even blanks and comments?
44 sectionOpen -- The string that denotes the start of this section. You
45 must start your tag with a percent sign.
46 timesSeen -- This attribute is for informational purposes only. It is
47 incremented every time handleHeader is called to keep
48 track of the number of times a section of this type is
49 seen.
50 """
51 allLines = False
52 sectionOpen = ""
53 timesSeen = 0
54
55 def __init__(self, handler, **kwargs):
56 """Create a new Script instance. At the least, you must pass in an
57 instance of a baseHandler subclass.
58
59 Valid kwargs:
60
61 dataObj --
62 """
63 self.handler = handler
64
65 self.version = self.handler.version
66
67 self.dataObj = kwargs.get("dataObj", None)
68
69 def finalize(self):
70 """This method is called when the %end tag for a section is seen. It
71 is not required to be provided.
72 """
73 pass
74
75 def handleLine(self, line):
76 """This method is called for every line of a section. Take whatever
77 action is appropriate. While this method is not required to be
78 provided, not providing it does not make a whole lot of sense.
79
80 Arguments:
81
82 line -- The complete line, with any trailing newline.
83 """
84 pass
85
86 def handleHeader(self, lineno, args):
87 """This method is called when the opening tag for a section is seen.
88 Not all sections will need this method, though all provided with
89 kickstart include one.
90
91 Arguments:
92
93 args -- A list of all strings passed as arguments to the section
94 opening tag.
95 """
96 self.timesSeen += 1
97
98class NullSection(Section):
99 """This defines a section that pykickstart will recognize but do nothing
100 with. If the parser runs across a %section that has no object registered,
101 it will raise an error. Sometimes, you may want to simply ignore those
102 sections instead. This class is useful for that purpose.
103 """
104 def __init__(self, *args, **kwargs):
105 """Create a new NullSection instance. You must pass a sectionOpen
106 parameter (including a leading '%') for the section you wish to
107 ignore.
108 """
109 Section.__init__(self, *args, **kwargs)
110 self.sectionOpen = kwargs.get("sectionOpen")
111
112class ScriptSection(Section):
113 allLines = True
114
115 def __init__(self, *args, **kwargs):
116 Section.__init__(self, *args, **kwargs)
117 self._script = {}
118 self._resetScript()
119
120 def _getParser(self):
121 op = KSOptionParser(self.version)
122 op.add_option("--erroronfail", dest="errorOnFail", action="store_true",
123 default=False)
124 op.add_option("--interpreter", dest="interpreter", default="/bin/sh")
125 op.add_option("--log", "--logfile", dest="log")
126 return op
127
128 def _resetScript(self):
129 self._script = {"interp": "/bin/sh", "log": None, "errorOnFail": False,
130 "lineno": None, "chroot": False, "body": []}
131
132 def handleLine(self, line):
133 self._script["body"].append(line)
134
135 def finalize(self):
136 if " ".join(self._script["body"]).strip() == "":
137 return
138
139 kwargs = {"interp": self._script["interp"],
140 "inChroot": self._script["chroot"],
141 "lineno": self._script["lineno"],
142 "logfile": self._script["log"],
143 "errorOnFail": self._script["errorOnFail"],
144 "type": self._script["type"]}
145
146 s = self.dataObj (self._script["body"], **kwargs)
147 self._resetScript()
148
149 if self.handler:
150 self.handler.scripts.append(s)
151
152 def handleHeader(self, lineno, args):
153 """Process the arguments to a %pre/%post/%traceback header for later
154 setting on a Script instance once the end of the script is found.
155 This method may be overridden in a subclass if necessary.
156 """
157 Section.handleHeader(self, lineno, args)
158 op = self._getParser()
159
160 (opts, extra) = op.parse_args(args=args[1:], lineno=lineno)
161
162 self._script["interp"] = opts.interpreter
163 self._script["lineno"] = lineno
164 self._script["log"] = opts.log
165 self._script["errorOnFail"] = opts.errorOnFail
166 if hasattr(opts, "nochroot"):
167 self._script["chroot"] = not opts.nochroot
168
169class PreScriptSection(ScriptSection):
170 sectionOpen = "%pre"
171
172 def _resetScript(self):
173 ScriptSection._resetScript(self)
174 self._script["type"] = KS_SCRIPT_PRE
175
176class PostScriptSection(ScriptSection):
177 sectionOpen = "%post"
178
179 def _getParser(self):
180 op = ScriptSection._getParser(self)
181 op.add_option("--nochroot", dest="nochroot", action="store_true",
182 default=False)
183 return op
184
185 def _resetScript(self):
186 ScriptSection._resetScript(self)
187 self._script["chroot"] = True
188 self._script["type"] = KS_SCRIPT_POST
189
190class TracebackScriptSection(ScriptSection):
191 sectionOpen = "%traceback"
192
193 def _resetScript(self):
194 ScriptSection._resetScript(self)
195 self._script["type"] = KS_SCRIPT_TRACEBACK
196
197class PackageSection(Section):
198 sectionOpen = "%packages"
199
200 def handleLine(self, line):
201 if not self.handler:
202 return
203
204 (h, s, t) = line.partition('#')
205 line = h.rstrip()
206
207 self.handler.packages.add([line])
208
209 def handleHeader(self, lineno, args):
210 """Process the arguments to the %packages header and set attributes
211 on the Version's Packages instance appropriate. This method may be
212 overridden in a subclass if necessary.
213 """
214 Section.handleHeader(self, lineno, args)
215 op = KSOptionParser(version=self.version)
216 op.add_option("--excludedocs", dest="excludedocs", action="store_true",
217 default=False)
218 op.add_option("--ignoremissing", dest="ignoremissing",
219 action="store_true", default=False)
220 op.add_option("--nobase", dest="nobase", action="store_true",
221 default=False)
222 op.add_option("--ignoredeps", dest="resolveDeps", action="store_false",
223 deprecated=FC4, removed=F9)
224 op.add_option("--resolvedeps", dest="resolveDeps", action="store_true",
225 deprecated=FC4, removed=F9)
226 op.add_option("--default", dest="defaultPackages", action="store_true",
227 default=False, introduced=F7)
228 op.add_option("--instLangs", dest="instLangs", type="string",
229 default="", introduced=F9)
230
231 (opts, extra) = op.parse_args(args=args[1:], lineno=lineno)
232
233 self.handler.packages.excludeDocs = opts.excludedocs
234 self.handler.packages.addBase = not opts.nobase
235 if opts.ignoremissing:
236 self.handler.packages.handleMissing = KS_MISSING_IGNORE
237 else:
238 self.handler.packages.handleMissing = KS_MISSING_PROMPT
239
240 if opts.defaultPackages:
241 self.handler.packages.default = True
242
243 if opts.instLangs:
244 self.handler.packages.instLangs = opts.instLangs
diff --git a/scripts/lib/wic/3rdparty/pykickstart/version.py b/scripts/lib/wic/3rdparty/pykickstart/version.py
new file mode 100644
index 0000000000..8a8e6aad22
--- /dev/null
+++ b/scripts/lib/wic/3rdparty/pykickstart/version.py
@@ -0,0 +1,168 @@
1#
2# Chris Lumens <clumens@redhat.com>
3#
4# Copyright 2006, 2007, 2008, 2009, 2010 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use, modify,
7# copy, or redistribute it subject to the terms and conditions of the GNU
8# General Public License v.2. This program is distributed in the hope that it
9# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
10# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11# See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along with
14# this program; if not, write to the Free Software Foundation, Inc., 51
15# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
16# trademarks that are incorporated in the source code or documentation are not
17# subject to the GNU General Public License and may only be used or replicated
18# with the express permission of Red Hat, Inc.
19#
20"""
21Methods for working with kickstart versions.
22
23This module defines several symbolic constants that specify kickstart syntax
24versions. Each version corresponds roughly to one release of Red Hat Linux,
25Red Hat Enterprise Linux, or Fedora Core as these are where most syntax
26changes take place.
27
28This module also exports several functions:
29
30 makeVersion - Given a version number, return an instance of the
31 matching handler class.
32
33 returnClassForVersion - Given a version number, return the matching
34 handler class. This does not return an
35 instance of that class, however.
36
37 stringToVersion - Convert a string representation of a version number
38 into the symbolic constant.
39
40 versionToString - Perform the reverse mapping.
41
42 versionFromFile - Read a kickstart file and determine the version of
43 syntax it uses. This requires the kickstart file to
44 have a version= comment in it.
45"""
46import imputil, re, sys
47
48import gettext
49_ = lambda x: gettext.ldgettext("pykickstart", x)
50
51from pykickstart.errors import KickstartVersionError
52
53# Symbolic names for internal version numbers.
54RHEL3 = 900
55FC3 = 1000
56RHEL4 = 1100
57FC4 = 2000
58FC5 = 3000
59FC6 = 4000
60RHEL5 = 4100
61F7 = 5000
62F8 = 6000
63F9 = 7000
64F10 = 8000
65F11 = 9000
66F12 = 10000
67F13 = 11000
68RHEL6 = 11100
69F14 = 12000
70F15 = 13000
71F16 = 14000
72
73# This always points at the latest version and is the default.
74DEVEL = F16
75
76# A one-to-one mapping from string representations to version numbers.
77versionMap = {
78 "DEVEL": DEVEL,
79 "FC3": FC3, "FC4": FC4, "FC5": FC5, "FC6": FC6, "F7": F7, "F8": F8,
80 "F9": F9, "F10": F10, "F11": F11, "F12": F12, "F13": F13,
81 "F14": F14, "F15": F15, "F16": F16,
82 "RHEL3": RHEL3, "RHEL4": RHEL4, "RHEL5": RHEL5, "RHEL6": RHEL6
83}
84
85def stringToVersion(s):
86 """Convert string into one of the provided version constants. Raises
87 KickstartVersionError if string does not match anything.
88 """
89 # First try these short forms.
90 try:
91 return versionMap[s.upper()]
92 except KeyError:
93 pass
94
95 # Now try the Fedora versions.
96 m = re.match("^fedora.* (\d+)$", s, re.I)
97
98 if m and m.group(1):
99 if versionMap.has_key("FC" + m.group(1)):
100 return versionMap["FC" + m.group(1)]
101 elif versionMap.has_key("F" + m.group(1)):
102 return versionMap["F" + m.group(1)]
103 else:
104 raise KickstartVersionError(_("Unsupported version specified: %s") % s)
105
106 # Now try the RHEL versions.
107 m = re.match("^red hat enterprise linux.* (\d+)([\.\d]*)$", s, re.I)
108
109 if m and m.group(1):
110 if versionMap.has_key("RHEL" + m.group(1)):
111 return versionMap["RHEL" + m.group(1)]
112 else:
113 raise KickstartVersionError(_("Unsupported version specified: %s") % s)
114
115 # If nothing else worked, we're out of options.
116 raise KickstartVersionError(_("Unsupported version specified: %s") % s)
117
118def versionToString(version, skipDevel=False):
119 """Convert version into a string representation of the version number.
120 This is the reverse operation of stringToVersion. Raises
121 KickstartVersionError if version does not match anything.
122 """
123 if not skipDevel and version == versionMap["DEVEL"]:
124 return "DEVEL"
125
126 for (key, val) in versionMap.iteritems():
127 if key == "DEVEL":
128 continue
129 elif val == version:
130 return key
131
132 raise KickstartVersionError(_("Unsupported version specified: %s") % version)
133
134def returnClassForVersion(version=DEVEL):
135 """Return the class of the syntax handler for version. version can be
136 either a string or the matching constant. Raises KickstartValueError
137 if version does not match anything.
138 """
139 try:
140 version = int(version)
141 module = "%s" % versionToString(version, skipDevel=True)
142 except ValueError:
143 module = "%s" % version
144 version = stringToVersion(version)
145
146 module = module.lower()
147
148 try:
149 import pykickstart.handlers
150 sys.path.extend(pykickstart.handlers.__path__)
151 found = imputil.imp.find_module(module)
152 loaded = imputil.imp.load_module(module, found[0], found[1], found[2])
153
154 for (k, v) in loaded.__dict__.iteritems():
155 if k.lower().endswith("%shandler" % module):
156 return v
157 except:
158 raise KickstartVersionError(_("Unsupported version specified: %s") % version)
159
160def makeVersion(version=DEVEL):
161 """Return a new instance of the syntax handler for version. version can be
162 either a string or the matching constant. This function is useful for
163 standalone programs which just need to handle a specific version of
164 kickstart syntax (as provided by a command line argument, for example)
165 and need to instantiate the correct object.
166 """
167 cl = returnClassForVersion(version)
168 return cl()
diff --git a/scripts/lib/wic/__init__.py b/scripts/lib/wic/__init__.py
new file mode 100644
index 0000000000..63c1d9c846
--- /dev/null
+++ b/scripts/lib/wic/__init__.py
@@ -0,0 +1,4 @@
1import os, sys
2
3cur_path = os.path.dirname(__file__) or '.'
4sys.path.insert(0, cur_path + '/3rdparty')
diff --git a/scripts/lib/wic/__version__.py b/scripts/lib/wic/__version__.py
new file mode 100644
index 0000000000..5452a46712
--- /dev/null
+++ b/scripts/lib/wic/__version__.py
@@ -0,0 +1 @@
VERSION = "2.00"
diff --git a/scripts/lib/wic/conf.py b/scripts/lib/wic/conf.py
new file mode 100644
index 0000000000..be34355ce4
--- /dev/null
+++ b/scripts/lib/wic/conf.py
@@ -0,0 +1,102 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os, sys, re
19import ConfigParser
20
21from wic import msger
22from wic import kickstart
23from wic.utils import misc, runner, errors
24
25
26def get_siteconf():
27 wic_path = os.path.dirname(__file__)
28 eos = wic_path.find('scripts') + len('scripts')
29 scripts_path = wic_path[:eos]
30
31 return scripts_path + "/lib/image/config/wic.conf"
32
33class ConfigMgr(object):
34 DEFAULTS = {'common': {
35 "distro_name": "Default Distribution",
36 "plugin_dir": "/usr/lib/wic/plugins", # TODO use prefix also?
37 },
38 'create': {
39 "tmpdir": '/var/tmp/wic',
40 "outdir": './wic-output',
41
42 "release": None,
43 "logfile": None,
44 "name_prefix": None,
45 "name_suffix": None,
46 },
47 }
48
49 # make the manager class as singleton
50 _instance = None
51 def __new__(cls, *args, **kwargs):
52 if not cls._instance:
53 cls._instance = super(ConfigMgr, cls).__new__(cls, *args, **kwargs)
54
55 return cls._instance
56
57 def __init__(self, ksconf=None, siteconf=None):
58 # reset config options
59 self.reset()
60
61 if not siteconf:
62 siteconf = get_siteconf()
63
64 # initial options from siteconf
65 self._siteconf = siteconf
66
67 if ksconf:
68 self._ksconf = ksconf
69
70 def reset(self):
71 self.__ksconf = None
72 self.__siteconf = None
73
74 # initialize the values with defaults
75 for sec, vals in self.DEFAULTS.iteritems():
76 setattr(self, sec, vals)
77
78 def __set_ksconf(self, ksconf):
79 if not os.path.isfile(ksconf):
80 msger.error('Cannot find ks file: %s' % ksconf)
81
82 self.__ksconf = ksconf
83 self._parse_kickstart(ksconf)
84 def __get_ksconf(self):
85 return self.__ksconf
86 _ksconf = property(__get_ksconf, __set_ksconf)
87
88 def _parse_kickstart(self, ksconf=None):
89 if not ksconf:
90 return
91
92 ks = kickstart.read_kickstart(ksconf)
93
94 self.create['ks'] = ks
95 self.create['name'] = os.path.splitext(os.path.basename(ksconf))[0]
96
97 self.create['name'] = misc.build_name(ksconf,
98 self.create['release'],
99 self.create['name_prefix'],
100 self.create['name_suffix'])
101
102configmgr = ConfigMgr()
diff --git a/scripts/lib/wic/creator.py b/scripts/lib/wic/creator.py
new file mode 100644
index 0000000000..2219377b38
--- /dev/null
+++ b/scripts/lib/wic/creator.py
@@ -0,0 +1,187 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os, sys, re
19from optparse import SUPPRESS_HELP
20
21from wic import msger
22from wic.utils import cmdln, errors
23from wic.conf import configmgr
24from wic.plugin import pluginmgr
25
26
27class Creator(cmdln.Cmdln):
28 """${name}: create an image
29
30 Usage:
31 ${name} SUBCOMMAND <ksfile> [OPTS]
32
33 ${command_list}
34 ${option_list}
35 """
36
37 name = 'wic create(cr)'
38
39 def __init__(self, *args, **kwargs):
40 cmdln.Cmdln.__init__(self, *args, **kwargs)
41 self._subcmds = []
42
43 # get cmds from pluginmgr
44 # mix-in do_subcmd interface
45 for subcmd, klass in pluginmgr.get_plugins('imager').iteritems():
46 if not hasattr(klass, 'do_create'):
47 msger.warning("Unsupported subcmd: %s" % subcmd)
48 continue
49
50 func = getattr(klass, 'do_create')
51 setattr(self.__class__, "do_"+subcmd, func)
52 self._subcmds.append(subcmd)
53
54 def get_optparser(self):
55 optparser = cmdln.CmdlnOptionParser(self)
56 optparser.add_option('-d', '--debug', action='store_true',
57 dest='debug',
58 help=SUPPRESS_HELP)
59 optparser.add_option('-v', '--verbose', action='store_true',
60 dest='verbose',
61 help=SUPPRESS_HELP)
62 optparser.add_option('', '--logfile', type='string', dest='logfile',
63 default=None,
64 help='Path of logfile')
65 optparser.add_option('-c', '--config', type='string', dest='config',
66 default=None,
67 help='Specify config file for wic')
68 optparser.add_option('-o', '--outdir', type='string', action='store',
69 dest='outdir', default=None,
70 help='Output directory')
71 optparser.add_option('', '--tmpfs', action='store_true', dest='enabletmpfs',
72 help='Setup tmpdir as tmpfs to accelerate, experimental'
73 ' feature, use it if you have more than 4G memory')
74 return optparser
75
76 def preoptparse(self, argv):
77 optparser = self.get_optparser()
78
79 largs = []
80 rargs = []
81 while argv:
82 arg = argv.pop(0)
83
84 if arg in ('-h', '--help'):
85 rargs.append(arg)
86
87 elif optparser.has_option(arg):
88 largs.append(arg)
89
90 if optparser.get_option(arg).takes_value():
91 try:
92 largs.append(argv.pop(0))
93 except IndexError:
94 raise errors.Usage("option %s requires arguments" % arg)
95
96 else:
97 if arg.startswith("--"):
98 if "=" in arg:
99 opt = arg.split("=")[0]
100 else:
101 opt = None
102 elif arg.startswith("-") and len(arg) > 2:
103 opt = arg[0:2]
104 else:
105 opt = None
106
107 if opt and optparser.has_option(opt):
108 largs.append(arg)
109 else:
110 rargs.append(arg)
111
112 return largs + rargs
113
114 def postoptparse(self):
115 abspath = lambda pth: os.path.abspath(os.path.expanduser(pth))
116
117 if self.options.verbose:
118 msger.set_loglevel('verbose')
119 if self.options.debug:
120 msger.set_loglevel('debug')
121
122 if self.options.logfile:
123 logfile_abs_path = abspath(self.options.logfile)
124 if os.path.isdir(logfile_abs_path):
125 raise errors.Usage("logfile's path %s should be file"
126 % self.options.logfile)
127 if not os.path.exists(os.path.dirname(logfile_abs_path)):
128 os.makedirs(os.path.dirname(logfile_abs_path))
129 msger.set_interactive(False)
130 msger.set_logfile(logfile_abs_path)
131 configmgr.create['logfile'] = self.options.logfile
132
133 if self.options.config:
134 configmgr.reset()
135 configmgr._siteconf = self.options.config
136
137 if self.options.outdir is not None:
138 configmgr.create['outdir'] = abspath(self.options.outdir)
139
140 cdir = 'outdir'
141 if os.path.exists(configmgr.create[cdir]) \
142 and not os.path.isdir(configmgr.create[cdir]):
143 msger.error('Invalid directory specified: %s' \
144 % configmgr.create[cdir])
145
146 if self.options.enabletmpfs:
147 configmgr.create['enabletmpfs'] = self.options.enabletmpfs
148
149 def main(self, argv=None):
150 if argv is None:
151 argv = sys.argv
152 else:
153 argv = argv[:] # don't modify caller's list
154
155 self.optparser = self.get_optparser()
156 if self.optparser:
157 try:
158 argv = self.preoptparse(argv)
159 self.options, args = self.optparser.parse_args(argv)
160
161 except cmdln.CmdlnUserError, ex:
162 msg = "%s: %s\nTry '%s help' for info.\n"\
163 % (self.name, ex, self.name)
164 msger.error(msg)
165
166 except cmdln.StopOptionProcessing, ex:
167 return 0
168 else:
169 # optparser=None means no process for opts
170 self.options, args = None, argv[1:]
171
172 if not args:
173 return self.emptyline()
174
175 self.postoptparse()
176
177 return self.cmd(args)
178
179 def precmd(self, argv): # check help before cmd
180
181 if '-h' in argv or '?' in argv or '--help' in argv or 'help' in argv:
182 return argv
183
184 if len(argv) == 1:
185 return ['help', argv[0]]
186
187 return argv
diff --git a/scripts/lib/wic/imager/__init__.py b/scripts/lib/wic/imager/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/lib/wic/imager/__init__.py
diff --git a/scripts/lib/wic/imager/baseimager.py b/scripts/lib/wic/imager/baseimager.py
new file mode 100644
index 0000000000..e8305272a2
--- /dev/null
+++ b/scripts/lib/wic/imager/baseimager.py
@@ -0,0 +1,193 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2007 Red Hat Inc.
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19from __future__ import with_statement
20import os, sys
21import tempfile
22import shutil
23
24from wic import kickstart
25from wic import msger
26from wic.utils.errors import CreatorError
27from wic.utils import misc, runner, fs_related as fs
28
29class BaseImageCreator(object):
30 """Base class for image creation.
31
32 BaseImageCreator is the simplest creator class available; it will
33 create a system image according to the supplied kickstart file.
34
35 e.g.
36
37 import wic.imgcreate as imgcreate
38 ks = imgcreate.read_kickstart("foo.ks")
39 imgcreate.ImageCreator(ks, "foo").create()
40 """
41
42 def __del__(self):
43 self.cleanup()
44
45 def __init__(self, createopts = None):
46 """Initialize an ImageCreator instance.
47
48 ks -- a pykickstart.KickstartParser instance; this instance will be
49 used to drive the install by e.g. providing the list of packages
50 to be installed, the system configuration and %post scripts
51
52 name -- a name for the image; used for e.g. image filenames or
53 filesystem labels
54 """
55
56 self.__builddir = None
57
58 self.ks = None
59 self.name = "target"
60 self.tmpdir = "/var/tmp/wic"
61 self.workdir = "/var/tmp/wic/build"
62
63 # setup tmpfs tmpdir when enabletmpfs is True
64 self.enabletmpfs = False
65
66 if createopts:
67 # Mapping table for variables that have different names.
68 optmap = {"outdir" : "destdir",
69 }
70
71 # update setting from createopts
72 for key in createopts.keys():
73 if key in optmap:
74 option = optmap[key]
75 else:
76 option = key
77 setattr(self, option, createopts[key])
78
79 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
80
81 self._dep_checks = ["ls", "bash", "cp", "echo"]
82
83 # Output image file names
84 self.outimage = []
85
86 # No ks provided when called by convertor, so skip the dependency check
87 if self.ks:
88 # If we have btrfs partition we need to check necessary tools
89 for part in self.ks.handler.partition.partitions:
90 if part.fstype and part.fstype == "btrfs":
91 self._dep_checks.append("mkfs.btrfs")
92 break
93
94 # make sure the specified tmpdir and cachedir exist
95 if not os.path.exists(self.tmpdir):
96 os.makedirs(self.tmpdir)
97
98
99 #
100 # Hooks for subclasses
101 #
102 def _create(self):
103 """Create partitions for the disk image(s)
104
105 This is the hook where subclasses may create the partitions
106 that will be assembled into disk image(s).
107
108 There is no default implementation.
109 """
110 pass
111
112 def _cleanup(self):
113 """Undo anything performed in _create().
114
115 This is the hook where subclasses must undo anything which was
116 done in _create().
117
118 There is no default implementation.
119
120 """
121 pass
122
123 #
124 # Actual implementation
125 #
126 def __ensure_builddir(self):
127 if not self.__builddir is None:
128 return
129
130 try:
131 self.workdir = os.path.join(self.tmpdir, "build")
132 if not os.path.exists(self.workdir):
133 os.makedirs(self.workdir)
134 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
135 prefix = "imgcreate-")
136 except OSError, (err, msg):
137 raise CreatorError("Failed create build directory in %s: %s" %
138 (self.tmpdir, msg))
139
140 def __setup_tmpdir(self):
141 if not self.enabletmpfs:
142 return
143
144 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
145
146 def __clean_tmpdir(self):
147 if not self.enabletmpfs:
148 return
149
150 runner.show('umount -l %s' % self.workdir)
151
152 def create(self):
153 """Create partitions for the disk image(s)
154
155 Create the partitions that will be assembled into disk
156 image(s).
157 """
158 self.__setup_tmpdir()
159 self.__ensure_builddir()
160
161 self._create()
162
163 def cleanup(self):
164 """Undo anything performed in create().
165
166 Note, make sure to call this method once finished with the creator
167 instance in order to ensure no stale files are left on the host e.g.:
168
169 creator = ImageCreator(ks, name)
170 try:
171 creator.create()
172 finally:
173 creator.cleanup()
174
175 """
176 if not self.__builddir:
177 return
178
179 self._cleanup()
180
181 shutil.rmtree(self.__builddir, ignore_errors = True)
182 self.__builddir = None
183
184 self.__clean_tmpdir()
185
186
187 def print_outimage_info(self):
188 msg = "The new image can be found here:\n"
189 self.outimage.sort()
190 for file in self.outimage:
191 msg += ' %s\n' % os.path.abspath(file)
192
193 msger.info(msg)
diff --git a/scripts/lib/wic/imager/direct.py b/scripts/lib/wic/imager/direct.py
new file mode 100644
index 0000000000..d0ae504daf
--- /dev/null
+++ b/scripts/lib/wic/imager/direct.py
@@ -0,0 +1,363 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'direct' image creator class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import os
28import stat
29import shutil
30
31from wic import kickstart, msger
32from wic.utils import fs_related, runner, misc
33from wic.utils.partitionedfs import Image
34from wic.utils.errors import CreatorError, ImageError
35from wic.imager.baseimager import BaseImageCreator
36from wic.utils.oe.misc import *
37from wic.plugin import pluginmgr
38
39disk_methods = {
40 "do_install_disk":None,
41}
42
43class DirectImageCreator(BaseImageCreator):
44 """
45 Installs a system into a file containing a partitioned disk image.
46
47 DirectImageCreator is an advanced ImageCreator subclass; an image
48 file is formatted with a partition table, each partition created
49 from a rootfs or other OpenEmbedded build artifact and dd'ed into
50 the virtual disk. The disk image can subsequently be dd'ed onto
51 media and used on actual hardware.
52 """
53
54 def __init__(self, oe_builddir, image_output_dir, rootfs_dir, bootimg_dir,
55 kernel_dir, native_sysroot, creatoropts=None):
56 """
57 Initialize a DirectImageCreator instance.
58
59 This method takes the same arguments as ImageCreator.__init__()
60 """
61 BaseImageCreator.__init__(self, creatoropts)
62
63 self.__image = None
64 self.__disks = {}
65 self.__disk_format = "direct"
66 self._disk_names = []
67 self._ptable_format = self.ks.handler.bootloader.ptable
68
69 self.oe_builddir = oe_builddir
70 if image_output_dir:
71 self.tmpdir = image_output_dir
72 self.rootfs_dir = rootfs_dir
73 self.bootimg_dir = bootimg_dir
74 self.kernel_dir = kernel_dir
75 self.native_sysroot = native_sysroot
76
77 def __write_fstab(self, image_rootfs):
78 """overriden to generate fstab (temporarily) in rootfs. This is called
79 from _create, make sure it doesn't get called from
80 BaseImage.create()
81 """
82 if image_rootfs is None:
83 return None
84
85 fstab = image_rootfs + "/etc/fstab"
86 if not os.path.isfile(fstab):
87 return None
88
89 parts = self._get_parts()
90
91 self._save_fstab(fstab)
92 fstab_lines = self._get_fstab(fstab, parts)
93 self._update_fstab(fstab_lines, parts)
94 self._write_fstab(fstab, fstab_lines)
95
96 return fstab
97
98 def _update_fstab(self, fstab_lines, parts):
99 """Assume partition order same as in wks"""
100 for num, p in enumerate(parts, 1):
101 if not p.mountpoint or p.mountpoint == "/" or p.mountpoint == "/boot":
102 continue
103 if self._ptable_format == 'msdos' and num > 3:
104 device_name = "/dev/" + p.disk + str(num + 1)
105 else:
106 device_name = "/dev/" + p.disk + str(num)
107
108 opts = "defaults"
109 if p.fsopts:
110 opts = p.fsopts
111
112 fstab_entry = device_name + "\t" + \
113 p.mountpoint + "\t" + \
114 p.fstype + "\t" + \
115 opts + "\t0\t0\n"
116 fstab_lines.append(fstab_entry)
117
118 def _write_fstab(self, fstab, fstab_lines):
119 fstab = open(fstab, "w")
120 for line in fstab_lines:
121 fstab.write(line)
122 fstab.close()
123
124 def _save_fstab(self, fstab):
125 """Save the current fstab in rootfs"""
126 shutil.copyfile(fstab, fstab + ".orig")
127
128 def _restore_fstab(self, fstab):
129 """Restore the saved fstab in rootfs"""
130 if fstab is None:
131 return
132 shutil.move(fstab + ".orig", fstab)
133
134 def _get_fstab(self, fstab, parts):
135 """Return the desired contents of /etc/fstab."""
136 f = open(fstab, "r")
137 fstab_contents = f.readlines()
138 f.close()
139
140 return fstab_contents
141
142 def set_bootimg_dir(self, bootimg_dir):
143 """
144 Accessor for bootimg_dir, the actual location used for the source
145 of the bootimg. Should be set by source plugins (only if they
146 change the default bootimg source) so the correct info gets
147 displayed for print_outimage_info().
148 """
149 self.bootimg_dir = bootimg_dir
150
151 def _get_parts(self):
152 if not self.ks:
153 raise CreatorError("Failed to get partition info, "
154 "please check your kickstart setting.")
155
156 # Set a default partition if no partition is given out
157 if not self.ks.handler.partition.partitions:
158 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
159 args = partstr.split()
160 pd = self.ks.handler.partition.parse(args[1:])
161 if pd not in self.ks.handler.partition.partitions:
162 self.ks.handler.partition.partitions.append(pd)
163
164 # partitions list from kickstart file
165 return kickstart.get_partitions(self.ks)
166
167 def get_disk_names(self):
168 """ Returns a list of physical target disk names (e.g., 'sdb') which
169 will be created. """
170
171 if self._disk_names:
172 return self._disk_names
173
174 #get partition info from ks handler
175 parts = self._get_parts()
176
177 for i in range(len(parts)):
178 if parts[i].disk:
179 disk_name = parts[i].disk
180 else:
181 raise CreatorError("Failed to create disks, no --ondisk "
182 "specified in partition line of ks file")
183
184 if parts[i].mountpoint and not parts[i].fstype:
185 raise CreatorError("Failed to create disks, no --fstype "
186 "specified for partition with mountpoint "
187 "'%s' in the ks file")
188
189 self._disk_names.append(disk_name)
190
191 return self._disk_names
192
193 def _full_name(self, name, extention):
194 """ Construct full file name for a file we generate. """
195 return "%s-%s.%s" % (self.name, name, extention)
196
197 def _full_path(self, path, name, extention):
198 """ Construct full file path to a file we generate. """
199 return os.path.join(path, self._full_name(name, extention))
200
201 def get_default_source_plugin(self):
202 """
203 The default source plugin i.e. the plugin that's consulted for
204 overall image generation tasks outside of any particular
205 partition. For convenience, we just hang it off the
206 bootloader handler since it's the one non-partition object in
207 any setup. By default the default plugin is set to the same
208 plugin as the /boot partition; since we hang it off the
209 bootloader object, the default can be explicitly set using the
210 --source bootloader param.
211 """
212 return self.ks.handler.bootloader.source
213
214 #
215 # Actual implemention
216 #
217 def _create(self):
218 """
219 For 'wic', we already have our build artifacts - we just create
220 filesystems from the artifacts directly and combine them into
221 a partitioned image.
222 """
223 parts = self._get_parts()
224
225 self.__image = Image()
226
227 for p in parts:
228 # as a convenience, set source to the boot partition source
229 # instead of forcing it to be set via bootloader --source
230 if not self.ks.handler.bootloader.source and p.mountpoint == "/boot":
231 self.ks.handler.bootloader.source = p.source
232
233 for p in parts:
234 # need to create the filesystems in order to get their
235 # sizes before we can add them and do the layout.
236 # Image.create() actually calls __format_disks() to create
237 # the disk images and carve out the partitions, then
238 # self.assemble() calls Image.assemble() which calls
239 # __write_partitition() for each partition to dd the fs
240 # into the partitions.
241
242 p.install_pkgs(self, self.workdir, self.oe_builddir, self.rootfs_dir,
243 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
244
245 fstab = self.__write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
246
247 p.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
248 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
249
250 self._restore_fstab(fstab)
251
252 self.__image.add_partition(int(p.size),
253 p.disk,
254 p.mountpoint,
255 p.source_file,
256 p.fstype,
257 p.label,
258 fsopts = p.fsopts,
259 boot = p.active,
260 align = p.align,
261 part_type = p.part_type)
262
263 self.__image.layout_partitions(self._ptable_format)
264
265 self.__imgdir = self.workdir
266 for disk_name, disk in self.__image.disks.items():
267 full_path = self._full_path(self.__imgdir, disk_name, "direct")
268 msger.debug("Adding disk %s as %s with size %s bytes" \
269 % (disk_name, full_path, disk['min_size']))
270 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
271 self.__disks[disk_name] = disk_obj
272 self.__image.add_disk(disk_name, disk_obj)
273
274 self.__image.create()
275
276 def assemble(self):
277 """
278 Assemble partitions into disk image(s)
279 """
280 for disk_name, disk in self.__image.disks.items():
281 full_path = self._full_path(self.__imgdir, disk_name, "direct")
282 msger.debug("Assembling disk %s as %s with size %s bytes" \
283 % (disk_name, full_path, disk['min_size']))
284 self.__image.assemble(full_path)
285
286 def finalize(self):
287 """
288 Finalize the disk image.
289
290 For example, prepare the image to be bootable by e.g.
291 creating and installing a bootloader configuration.
292
293 """
294 source_plugin = self.get_default_source_plugin()
295 if source_plugin:
296 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
297 for disk_name, disk in self.__image.disks.items():
298 self._source_methods["do_install_disk"](disk, disk_name, self,
299 self.workdir,
300 self.oe_builddir,
301 self.bootimg_dir,
302 self.kernel_dir,
303 self.native_sysroot)
304
305 def print_outimage_info(self):
306 """
307 Print the image(s) and artifacts used, for the user.
308 """
309 msg = "The new image(s) can be found here:\n"
310
311 parts = self._get_parts()
312
313 for disk_name, disk in self.__image.disks.items():
314 full_path = self._full_path(self.__imgdir, disk_name, "direct")
315 msg += ' %s\n\n' % full_path
316
317 msg += 'The following build artifacts were used to create the image(s):\n'
318 for p in parts:
319 if p.get_rootfs() is None:
320 continue
321 if p.mountpoint == '/':
322 str = ':'
323 else:
324 str = '["%s"]:' % p.label
325 msg += ' ROOTFS_DIR%s%s\n' % (str.ljust(20), p.get_rootfs())
326
327 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
328 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
329 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
330
331 msger.info(msg)
332
333 def _get_boot_config(self):
334 """
335 Return the rootdev/root_part_uuid (if specified by
336 --part-type)
337
338 Assume partition order same as in wks
339 """
340 rootdev = None
341 root_part_uuid = None
342 parts = self._get_parts()
343 for num, p in enumerate(parts, 1):
344 if p.mountpoint == "/":
345 part = ''
346 if p.disk.startswith('mmcblk'):
347 part = 'p'
348
349 if self._ptable_format == 'msdos' and num > 3:
350 rootdev = "/dev/%s%s%-d" % (p.disk, part, num + 1)
351 else:
352 rootdev = "/dev/%s%s%-d" % (p.disk, part, num)
353 root_part_uuid = p.part_type
354
355 return (rootdev, root_part_uuid)
356
357 def _cleanup(self):
358 if not self.__image is None:
359 try:
360 self.__image.cleanup()
361 except ImageError, err:
362 msger.warning("%s" % err)
363
diff --git a/scripts/lib/wic/kickstart/__init__.py b/scripts/lib/wic/kickstart/__init__.py
new file mode 100644
index 0000000000..600098293a
--- /dev/null
+++ b/scripts/lib/wic/kickstart/__init__.py
@@ -0,0 +1,125 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2007 Red Hat, Inc.
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19import os, sys, re
20import shutil
21import subprocess
22import string
23
24import pykickstart.sections as kssections
25import pykickstart.commands as kscommands
26import pykickstart.constants as ksconstants
27import pykickstart.errors as kserrors
28import pykickstart.parser as ksparser
29import pykickstart.version as ksversion
30from pykickstart.handlers.control import commandMap
31from pykickstart.handlers.control import dataMap
32
33from wic import msger
34from wic.utils import errors, misc, runner, fs_related as fs
35from custom_commands import wicboot, partition
36
37def read_kickstart(path):
38 """Parse a kickstart file and return a KickstartParser instance.
39
40 This is a simple utility function which takes a path to a kickstart file,
41 parses it and returns a pykickstart KickstartParser instance which can
42 be then passed to an ImageCreator constructor.
43
44 If an error occurs, a CreatorError exception is thrown.
45 """
46
47 #version = ksversion.makeVersion()
48 #ks = ksparser.KickstartParser(version)
49
50 using_version = ksversion.DEVEL
51 commandMap[using_version]["bootloader"] = wicboot.Wic_Bootloader
52 commandMap[using_version]["part"] = partition.Wic_Partition
53 commandMap[using_version]["partition"] = partition.Wic_Partition
54 dataMap[using_version]["PartData"] = partition.Wic_PartData
55 superclass = ksversion.returnClassForVersion(version=using_version)
56
57 class KSHandlers(superclass):
58 def __init__(self):
59 superclass.__init__(self, mapping=commandMap[using_version])
60
61 ks = ksparser.KickstartParser(KSHandlers(), errorsAreFatal=False)
62
63 try:
64 ks.readKickstart(path)
65 except (kserrors.KickstartParseError, kserrors.KickstartError), err:
66 if msger.ask("Errors occured on kickstart file, skip and continue?"):
67 msger.warning("%s" % err)
68 pass
69 else:
70 raise errors.KsError("%s" % err)
71
72 return ks
73
74def get_image_size(ks, default = None):
75 __size = 0
76 for p in ks.handler.partition.partitions:
77 if p.mountpoint == "/" and p.size:
78 __size = p.size
79 if __size > 0:
80 return int(__size) * 1024L * 1024L
81 else:
82 return default
83
84def get_image_fstype(ks, default = None):
85 for p in ks.handler.partition.partitions:
86 if p.mountpoint == "/" and p.fstype:
87 return p.fstype
88 return default
89
90def get_image_fsopts(ks, default = None):
91 for p in ks.handler.partition.partitions:
92 if p.mountpoint == "/" and p.fsopts:
93 return p.fsopts
94 return default
95
96def get_timeout(ks, default = None):
97 if not hasattr(ks.handler.bootloader, "timeout"):
98 return default
99 if ks.handler.bootloader.timeout is None:
100 return default
101 return int(ks.handler.bootloader.timeout)
102
103def get_kernel_args(ks, default = "ro rd.live.image"):
104 if not hasattr(ks.handler.bootloader, "appendLine"):
105 return default
106 if ks.handler.bootloader.appendLine is None:
107 return default
108 return "%s %s" %(default, ks.handler.bootloader.appendLine)
109
110def get_menu_args(ks, default = ""):
111 if not hasattr(ks.handler.bootloader, "menus"):
112 return default
113 if ks.handler.bootloader.menus in (None, ""):
114 return default
115 return "%s" % ks.handler.bootloader.menus
116
117def get_default_kernel(ks, default = None):
118 if not hasattr(ks.handler.bootloader, "default"):
119 return default
120 if not ks.handler.bootloader.default:
121 return default
122 return ks.handler.bootloader.default
123
124def get_partitions(ks):
125 return ks.handler.partition.partitions
diff --git a/scripts/lib/wic/kickstart/custom_commands/__init__.py b/scripts/lib/wic/kickstart/custom_commands/__init__.py
new file mode 100644
index 0000000000..f84c6d9e00
--- /dev/null
+++ b/scripts/lib/wic/kickstart/custom_commands/__init__.py
@@ -0,0 +1,10 @@
1from micpartition import Mic_Partition
2from micpartition import Mic_PartData
3from partition import Wic_Partition
4
5__all__ = (
6 "Mic_Partition",
7 "Mic_PartData",
8 "Wic_Partition",
9 "Wic_PartData",
10)
diff --git a/scripts/lib/wic/kickstart/custom_commands/micboot.py b/scripts/lib/wic/kickstart/custom_commands/micboot.py
new file mode 100644
index 0000000000..d162142506
--- /dev/null
+++ b/scripts/lib/wic/kickstart/custom_commands/micboot.py
@@ -0,0 +1,49 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2008, 2009, 2010 Intel, Inc.
4#
5# Anas Nashif
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms of the GNU General Public License as published by the Free
9# Software Foundation; version 2 of the License
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14# for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc., 59
18# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20from pykickstart.base import *
21from pykickstart.errors import *
22from pykickstart.options import *
23from pykickstart.commands.bootloader import *
24
25class Mic_Bootloader(F8_Bootloader):
26 def __init__(self, writePriority=10, appendLine="", driveorder=None,
27 forceLBA=False, location="", md5pass="", password="",
28 upgrade=False, menus=""):
29 F8_Bootloader.__init__(self, writePriority, appendLine, driveorder,
30 forceLBA, location, md5pass, password, upgrade)
31
32 self.menus = ""
33 self.ptable = "msdos"
34
35 def _getArgsAsStr(self):
36 ret = F8_Bootloader._getArgsAsStr(self)
37
38 if self.menus == "":
39 ret += " --menus=%s" %(self.menus,)
40 if self.ptable:
41 ret += " --ptable=\"%s\"" %(self.ptable,)
42 return ret
43
44 def _getParser(self):
45 op = F8_Bootloader._getParser(self)
46 op.add_option("--menus", dest="menus")
47 op.add_option("--ptable", dest="ptable", type="string")
48 return op
49
diff --git a/scripts/lib/wic/kickstart/custom_commands/micpartition.py b/scripts/lib/wic/kickstart/custom_commands/micpartition.py
new file mode 100644
index 0000000000..43d04f1294
--- /dev/null
+++ b/scripts/lib/wic/kickstart/custom_commands/micpartition.py
@@ -0,0 +1,57 @@
1#!/usr/bin/env python -tt
2#
3# Marko Saukko <marko.saukko@cybercom.com>
4#
5# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
6#
7# This copyrighted material is made available to anyone wishing to use, modify,
8# copy, or redistribute it subject to the terms and conditions of the GNU
9# General Public License v.2. This program is distributed in the hope that it
10# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
11# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12# See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along with
15# this program; if not, write to the Free Software Foundation, Inc., 51
16# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18from pykickstart.commands.partition import *
19
20class Mic_PartData(FC4_PartData):
21 removedKeywords = FC4_PartData.removedKeywords
22 removedAttrs = FC4_PartData.removedAttrs
23
24 def __init__(self, *args, **kwargs):
25 FC4_PartData.__init__(self, *args, **kwargs)
26 self.deleteRemovedAttrs()
27 self.align = kwargs.get("align", None)
28 self.extopts = kwargs.get("extopts", None)
29 self.part_type = kwargs.get("part_type", None)
30
31 def _getArgsAsStr(self):
32 retval = FC4_PartData._getArgsAsStr(self)
33
34 if self.align:
35 retval += " --align"
36 if self.extopts:
37 retval += " --extoptions=%s" % self.extopts
38 if self.part_type:
39 retval += " --part-type=%s" % self.part_type
40
41 return retval
42
43class Mic_Partition(FC4_Partition):
44 removedKeywords = FC4_Partition.removedKeywords
45 removedAttrs = FC4_Partition.removedAttrs
46
47 def _getParser(self):
48 op = FC4_Partition._getParser(self)
49 # The alignment value is given in kBytes. e.g., value 8 means that
50 # the partition is aligned to start from 8096 byte boundary.
51 op.add_option("--align", type="int", action="store", dest="align",
52 default=None)
53 op.add_option("--extoptions", type="string", action="store", dest="extopts",
54 default=None)
55 op.add_option("--part-type", type="string", action="store", dest="part_type",
56 default=None)
57 return op
diff --git a/scripts/lib/wic/kickstart/custom_commands/partition.py b/scripts/lib/wic/kickstart/custom_commands/partition.py
new file mode 100644
index 0000000000..03332194df
--- /dev/null
+++ b/scripts/lib/wic/kickstart/custom_commands/partition.py
@@ -0,0 +1,652 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This module provides the OpenEmbedded partition object definitions.
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import shutil
28import os
29import tempfile
30
31from pykickstart.commands.partition import *
32from wic.utils.oe.misc import *
33from wic.kickstart.custom_commands import *
34from wic.plugin import pluginmgr
35
36import os
37from mic.utils.oe.package_manager import *
38
39partition_methods = {
40 "do_install_pkgs":None,
41 "do_stage_partition":None,
42 "do_prepare_partition":None,
43 "do_configure_partition":None,
44}
45
46class Wic_PartData(Mic_PartData):
47 removedKeywords = Mic_PartData.removedKeywords
48 removedAttrs = Mic_PartData.removedAttrs
49
50 def __init__(self, *args, **kwargs):
51 Mic_PartData.__init__(self, *args, **kwargs)
52 self.deleteRemovedAttrs()
53 self.source = kwargs.get("source", None)
54 self.sourceparams = kwargs.get("sourceparams", None)
55 self.rootfs = kwargs.get("rootfs-dir", None)
56 self.source_file = ""
57 self.size = 0
58
59 def _getArgsAsStr(self):
60 retval = Mic_PartData._getArgsAsStr(self)
61
62 if self.source:
63 retval += " --source=%s" % self.source
64 if self.sourceparams:
65 retval += " --sourceparams=%s" % self.sourceparams
66 if self.rootfs:
67 retval += " --rootfs-dir=%s" % self.rootfs
68
69 return retval
70
71 def get_rootfs(self):
72 """
73 Acessor for rootfs dir
74 """
75 return self.rootfs
76
77 def set_rootfs(self, rootfs):
78 """
79 Acessor for actual rootfs dir, which must be set by source
80 plugins.
81 """
82 self.rootfs = rootfs
83
84 def get_size(self):
85 """
86 Accessor for partition size, 0 or --size before set_size().
87 """
88 return self.size
89
90 def set_size(self, size):
91 """
92 Accessor for actual partition size, which must be set by source
93 plugins.
94 """
95 self.size = size
96
97 def set_source_file(self, source_file):
98 """
99 Accessor for source_file, the location of the generated partition
100 image, which must be set by source plugins.
101 """
102 self.source_file = source_file
103
104 def get_extra_block_count(self, current_blocks):
105 """
106 The --size param is reflected in self.size (in MB), and we already
107 have current_blocks (1k) blocks, calculate and return the
108 number of (1k) blocks we need to add to get to --size, 0 if
109 we're already there or beyond.
110 """
111 msger.debug("Requested partition size for %s: %d" % \
112 (self.mountpoint, self.size))
113
114 if not self.size:
115 return 0
116
117 requested_blocks = self.size * 1024
118
119 msger.debug("Requested blocks %d, current_blocks %d" % \
120 (requested_blocks, current_blocks))
121
122 if requested_blocks > current_blocks:
123 return requested_blocks - current_blocks
124 else:
125 return 0
126
127 def install_pkgs(self, creator, cr_workdir, oe_builddir, rootfs_dir,
128 bootimg_dir, kernel_dir, native_sysroot):
129 """
130 Prepare content for individual partitions, installing packages.
131 """
132
133 if not self.source:
134 return
135
136 self._source_methods = pluginmgr.get_source_plugin_methods(self.source, partition_methods)
137 self._source_methods["do_install_pkgs"](self, creator,
138 cr_workdir,
139 oe_builddir,
140 rootfs_dir,
141 bootimg_dir,
142 kernel_dir,
143 native_sysroot)
144
145 def install_pkgs_ipk(self, cr_workdir, oe_builddir, rootfs_dir,
146 native_sysroot, packages, repourl):
147 """
148 Install packages specified into wks file using opkg package manager.
149 This method is dependend on bb module.
150 """
151
152 gVar = {}
153 gVar["DEPLOY_DIR_IPK"] = os.path.join(oe_builddir, "tmp/deploy/ipk")
154
155 # Run postinstall scripts even in offline mode
156 # Use the arch priority package rather than higher version one if more than one candidate is found.
157 #d.setVar("OPKG_ARGS", "--force_postinstall --prefer-arch-to-version")
158 gVar["OPKG_ARGS"] = "--force_postinstall"
159
160 # OPKG path relative to /output_path
161 gVar["OPKGLIBDIR"] = "var/lib"
162
163 source_url = repourl.split()
164
165 # Generate feed uri's names, it doesn't seem to matter what name they have
166 feed_uris = ""
167 cnt = 0
168 archs = ""
169 for url in source_url:
170 feed_uris += "cl_def_feed%d##%s\n" % (cnt, url)
171 cnt += 1
172 head, tail = os.path.split(url)
173 archs += " " + tail
174
175 # IPK_FEED_URIS with special formating defines the URI's used as source for packages
176 gVar['IPK_FEED_URIS'] = feed_uris
177
178 gVar['BUILD_IMAGES_FROM_FEEDS'] = "1"
179
180 # We need to provide sysroot for utilities
181 gVar['STAGING_DIR_NATIVE'] = native_sysroot
182
183 # Set WORKDIR for output
184 gVar['WORKDIR'] = cr_workdir
185
186 # Set TMPDIR for output
187 gVar['TMPDIR'] = os.path.join(cr_workdir, "tmp")
188
189 if 'ROOTFS_DIR' in rootfs_dir:
190 target_dir = rootfs_dir['ROOTFS_DIR']
191 elif os.path.isdir(rootfs_dir):
192 target_dir = rootfs_dir
193 else:
194 msg = "Couldn't find --rootfs-dir=%s connection"
195 msg += " or it is not a valid path, exiting"
196 msger.error(msg % rootfs_dir)
197
198 # Need native sysroot /usr/bin/ for opkg-cl
199 # chnage PATH var to avoid issues with host tools
200 defpath = os.environ['PATH']
201 os.environ['PATH'] = native_sysroot + "/usr/bin/" + ":/bin:/usr/bin:"
202
203 pseudo = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
204 pseudo += "export PSEUDO_LOCALSTATEDIR=%s/../pseudo;" % target_dir
205 pseudo += "export PSEUDO_PASSWD=%s;" % target_dir
206 pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
207 pseudo += "%s/usr/bin/pseudo " % native_sysroot
208
209 pm = WicOpkgPM(gVar,
210 target_dir,
211 'opkg.conf',
212 archs,
213 pseudo,
214 native_sysroot)
215
216 pm.update()
217
218 pm.install(packages)
219
220 os.environ['PATH'] += defpath + ":" + native_sysroot + "/usr/bin/"
221
222
223 def prepare(self, cr, cr_workdir, oe_builddir, rootfs_dir, bootimg_dir,
224 kernel_dir, native_sysroot):
225 """
226 Prepare content for individual partitions, depending on
227 partition command parameters.
228 """
229 self.sourceparams_dict = {}
230
231 if self.sourceparams:
232 self.sourceparams_dict = parse_sourceparams(self.sourceparams)
233
234 if not self.source:
235 if not self.size:
236 msger.error("The %s partition has a size of zero. Please specify a non-zero --size for that partition." % self.mountpoint)
237 if self.fstype and self.fstype == "swap":
238 self.prepare_swap_partition(cr_workdir, oe_builddir,
239 native_sysroot)
240 elif self.fstype:
241 self.prepare_empty_partition(cr_workdir, oe_builddir,
242 native_sysroot)
243 return
244
245 plugins = pluginmgr.get_source_plugins()
246
247 if self.source not in plugins:
248 msger.error("The '%s' --source specified for %s doesn't exist.\n\tSee 'wic list source-plugins' for a list of available --sources.\n\tSee 'wic help source-plugins' for details on adding a new source plugin." % (self.source, self.mountpoint))
249
250 self._source_methods = pluginmgr.get_source_plugin_methods(self.source, partition_methods)
251 self._source_methods["do_configure_partition"](self, self.sourceparams_dict,
252 cr, cr_workdir,
253 oe_builddir,
254 bootimg_dir,
255 kernel_dir,
256 native_sysroot)
257 self._source_methods["do_stage_partition"](self, self.sourceparams_dict,
258 cr, cr_workdir,
259 oe_builddir,
260 bootimg_dir, kernel_dir,
261 native_sysroot)
262 self._source_methods["do_prepare_partition"](self, self.sourceparams_dict,
263 cr, cr_workdir,
264 oe_builddir,
265 bootimg_dir, kernel_dir, rootfs_dir,
266 native_sysroot)
267
268 def prepare_rootfs_from_fs_image(self, cr_workdir, oe_builddir,
269 rootfs_dir):
270 """
271 Handle an already-created partition e.g. xxx.ext3
272 """
273 rootfs = oe_builddir
274 du_cmd = "du -Lbms %s" % rootfs
275 out = exec_cmd(du_cmd)
276 rootfs_size = out.split()[0]
277
278 self.size = rootfs_size
279 self.source_file = rootfs
280
281 def prepare_rootfs(self, cr_workdir, oe_builddir, rootfs_dir,
282 native_sysroot):
283 """
284 Prepare content for a rootfs partition i.e. create a partition
285 and fill it from a /rootfs dir.
286
287 Currently handles ext2/3/4, btrfs and vfat.
288 """
289 pseudo = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
290 pseudo += "export PSEUDO_LOCALSTATEDIR=%s/../pseudo;" % rootfs_dir
291 pseudo += "export PSEUDO_PASSWD=%s;" % rootfs_dir
292 pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
293 pseudo += "%s/usr/bin/pseudo " % native_sysroot
294
295 if self.fstype.startswith("ext"):
296 return self.prepare_rootfs_ext(cr_workdir, oe_builddir,
297 rootfs_dir, native_sysroot,
298 pseudo)
299 elif self.fstype.startswith("btrfs"):
300 return self.prepare_rootfs_btrfs(cr_workdir, oe_builddir,
301 rootfs_dir, native_sysroot,
302 pseudo)
303
304 elif self.fstype.startswith("vfat"):
305 return self.prepare_rootfs_vfat(cr_workdir, oe_builddir,
306 rootfs_dir, native_sysroot,
307 pseudo)
308 elif self.fstype.startswith("squashfs"):
309 return self.prepare_rootfs_squashfs(cr_workdir, oe_builddir,
310 rootfs_dir, native_sysroot,
311 pseudo)
312
313 def prepare_rootfs_ext(self, cr_workdir, oe_builddir, rootfs_dir,
314 native_sysroot, pseudo):
315 """
316 Prepare content for an ext2/3/4 rootfs partition.
317 """
318
319 image_rootfs = rootfs_dir
320 rootfs = "%s/rootfs_%s.%s" % (cr_workdir, self.label ,self.fstype)
321
322 du_cmd = "du -ks %s" % image_rootfs
323 out = exec_cmd(du_cmd)
324 actual_rootfs_size = int(out.split()[0])
325
326 extra_blocks = self.get_extra_block_count(actual_rootfs_size)
327
328 if extra_blocks < IMAGE_EXTRA_SPACE:
329 extra_blocks = IMAGE_EXTRA_SPACE
330
331 rootfs_size = actual_rootfs_size + extra_blocks
332 rootfs_size *= IMAGE_OVERHEAD_FACTOR
333
334 msger.debug("Added %d extra blocks to %s to get to %d total blocks" % \
335 (extra_blocks, self.mountpoint, rootfs_size))
336
337 dd_cmd = "dd if=/dev/zero of=%s bs=1024 seek=%d count=0 bs=1k" % \
338 (rootfs, rootfs_size)
339 exec_cmd(dd_cmd)
340
341 extra_imagecmd = "-i 8192"
342
343 mkfs_cmd = "mkfs.%s -F %s %s -d %s" % \
344 (self.fstype, extra_imagecmd, rootfs, image_rootfs)
345 (rc, out) = exec_native_cmd(pseudo + mkfs_cmd, native_sysroot)
346 if rc:
347 print "rootfs_dir: %s" % rootfs_dir
348 msger.error("ERROR: mkfs.%s returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details) when creating filesystem from rootfs directory: %s" % (self.fstype, rc, rootfs_dir))
349
350 # get the rootfs size in the right units for kickstart (Mb)
351 du_cmd = "du -Lbms %s" % rootfs
352 out = exec_cmd(du_cmd)
353 rootfs_size = out.split()[0]
354
355 self.size = rootfs_size
356 self.source_file = rootfs
357
358 return 0
359
360 def prepare_for_uboot(self, arch, cr_workdir, oe_builddir, rootfs_dir,
361 native_sysroot):
362 """
363 Generates u-boot image from source_file( ext2/3/4 )
364
365 """
366 pseudo = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
367 pseudo += "export PSEUDO_LOCALSTATEDIR=%s/../pseudo;" % rootfs_dir
368 pseudo += "export PSEUDO_PASSWD=%s;" % rootfs_dir
369 pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
370 pseudo += "%s/usr/bin/pseudo " % native_sysroot
371
372 # 1) compress image
373 rootfs = self.source_file
374 rootfs_gzip = "%s.gz" % rootfs
375 gzip_cmd = "gzip -f -9 -c %s > %s" % (rootfs, rootfs_gzip)
376 rc, out = exec_native_cmd(pseudo + gzip_cmd, native_sysroot)
377
378 # 2) image for U-Boot
379 rootfs_uboot = "%s.u-boot" % rootfs_gzip
380 mkimage_cmd = "mkimage -A %s -O linux -T ramdisk -C gzip -n %s -d %s %s" % \
381 (arch, self.label, rootfs_gzip, rootfs_uboot)
382 rc, out = exec_native_cmd(pseudo + mkimage_cmd, native_sysroot)
383
384 msger.info("\n\n\tThe new U-Boot ramdisk image can be found here:\n\t\t%s\n\n" % rootfs_uboot)
385
386 return 0
387
388 def prepare_rootfs_btrfs(self, cr_workdir, oe_builddir, rootfs_dir,
389 native_sysroot, pseudo):
390 """
391 Prepare content for a btrfs rootfs partition.
392
393 Currently handles ext2/3/4 and btrfs.
394 """
395 image_rootfs = rootfs_dir
396 rootfs = "%s/rootfs_%s.%s" % (cr_workdir, self.label, self.fstype)
397
398 du_cmd = "du -ks %s" % image_rootfs
399 out = exec_cmd(du_cmd)
400 actual_rootfs_size = int(out.split()[0])
401
402 extra_blocks = self.get_extra_block_count(actual_rootfs_size)
403
404 if extra_blocks < IMAGE_EXTRA_SPACE:
405 extra_blocks = IMAGE_EXTRA_SPACE
406
407 rootfs_size = actual_rootfs_size + extra_blocks
408 rootfs_size *= IMAGE_OVERHEAD_FACTOR
409
410 msger.debug("Added %d extra blocks to %s to get to %d total blocks" % \
411 (extra_blocks, self.mountpoint, rootfs_size))
412
413 dd_cmd = "dd if=/dev/zero of=%s bs=1024 seek=%d count=0 bs=1k" % \
414 (rootfs, rootfs_size)
415 exec_cmd(dd_cmd)
416
417 mkfs_cmd = "mkfs.%s -b %d -r %s %s" % \
418 (self.fstype, rootfs_size * 1024, image_rootfs, rootfs)
419 (rc, out) = exec_native_cmd(pseudo + mkfs_cmd, native_sysroot)
420 if rc:
421 msger.error("ERROR: mkfs.%s returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details) when creating filesystem from rootfs directory: %s" % (self.fstype, rc, rootfs_dir))
422
423 # get the rootfs size in the right units for kickstart (Mb)
424 du_cmd = "du -Lbms %s" % rootfs
425 out = exec_cmd(du_cmd)
426 rootfs_size = out.split()[0]
427
428 self.size = rootfs_size
429 self.source_file = rootfs
430
431 def prepare_rootfs_vfat(self, cr_workdir, oe_builddir, rootfs_dir,
432 native_sysroot, pseudo):
433 """
434 Prepare content for a vfat rootfs partition.
435 """
436 image_rootfs = rootfs_dir
437 rootfs = "%s/rootfs_%s.%s" % (cr_workdir, self.label, self.fstype)
438
439 du_cmd = "du -bks %s" % image_rootfs
440 out = exec_cmd(du_cmd)
441 blocks = int(out.split()[0])
442
443 extra_blocks = self.get_extra_block_count(blocks)
444
445 if extra_blocks < IMAGE_EXTRA_SPACE:
446 extra_blocks = IMAGE_EXTRA_SPACE
447
448 blocks += extra_blocks
449
450 msger.debug("Added %d extra blocks to %s to get to %d total blocks" % \
451 (extra_blocks, self.mountpoint, blocks))
452
453 # Ensure total sectors is an integral number of sectors per
454 # track or mcopy will complain. Sectors are 512 bytes, and we
455 # generate images with 32 sectors per track. This calculation
456 # is done in blocks, thus the mod by 16 instead of 32. Apply
457 # sector count fix only when needed.
458 if blocks % 16 != 0:
459 blocks += (16 - (blocks % 16))
460
461 dosfs_cmd = "mkdosfs -n boot -S 512 -C %s %d" % (rootfs, blocks)
462 exec_native_cmd(dosfs_cmd, native_sysroot)
463
464 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (rootfs, image_rootfs)
465 rc, out = exec_native_cmd(mcopy_cmd, native_sysroot)
466 if rc:
467 msger.error("ERROR: mcopy returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details)" % rc)
468
469 chmod_cmd = "chmod 644 %s" % rootfs
470 exec_cmd(chmod_cmd)
471
472 # get the rootfs size in the right units for kickstart (Mb)
473 du_cmd = "du -Lbms %s" % rootfs
474 out = exec_cmd(du_cmd)
475 rootfs_size = out.split()[0]
476
477 self.set_size(rootfs_size)
478 self.set_source_file(rootfs)
479
480 def prepare_rootfs_squashfs(self, cr_workdir, oe_builddir, rootfs_dir,
481 native_sysroot, pseudo):
482 """
483 Prepare content for a squashfs rootfs partition.
484 """
485 image_rootfs = rootfs_dir
486 rootfs = "%s/rootfs_%s.%s" % (cr_workdir, self.label ,self.fstype)
487
488 squashfs_cmd = "mksquashfs %s %s -noappend" % \
489 (image_rootfs, rootfs)
490 exec_native_cmd(pseudo + squashfs_cmd, native_sysroot)
491
492 # get the rootfs size in the right units for kickstart (Mb)
493 du_cmd = "du -Lbms %s" % rootfs
494 out = exec_cmd(du_cmd)
495 rootfs_size = out.split()[0]
496
497 self.size = rootfs_size
498 self.source_file = rootfs
499
500 return 0
501
502 def prepare_empty_partition(self, cr_workdir, oe_builddir, native_sysroot):
503 """
504 Prepare an empty partition.
505 """
506 if self.fstype.startswith("ext"):
507 return self.prepare_empty_partition_ext(cr_workdir, oe_builddir,
508 native_sysroot)
509 elif self.fstype.startswith("btrfs"):
510 return self.prepare_empty_partition_btrfs(cr_workdir, oe_builddir,
511 native_sysroot)
512 elif self.fstype.startswith("vfat"):
513 return self.prepare_empty_partition_vfat(cr_workdir, oe_builddir,
514 native_sysroot)
515 elif self.fstype.startswith("squashfs"):
516 return self.prepare_empty_partition_squashfs(cr_workdir, oe_builddir,
517 native_sysroot)
518
519 def prepare_empty_partition_ext(self, cr_workdir, oe_builddir,
520 native_sysroot):
521 """
522 Prepare an empty ext2/3/4 partition.
523 """
524 fs = "%s/fs_%s.%s" % (cr_workdir, self.label, self.fstype)
525
526 dd_cmd = "dd if=/dev/zero of=%s bs=1M seek=%d count=0" % \
527 (fs, self.size)
528 exec_cmd(dd_cmd)
529
530 extra_imagecmd = "-i 8192"
531
532 mkfs_cmd = "mkfs.%s -F %s %s" % (self.fstype, extra_imagecmd, fs)
533 (rc, out) = exec_native_cmd(mkfs_cmd, native_sysroot)
534 if rc:
535 msger.error("ERROR: mkfs.%s returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details)" % (self.fstype, rc))
536
537 self.source_file = fs
538
539 return 0
540
541 def prepare_empty_partition_btrfs(self, cr_workdir, oe_builddir,
542 native_sysroot):
543 """
544 Prepare an empty btrfs partition.
545 """
546 fs = "%s/fs_%s.%s" % (cr_workdir, self.label, self.fstype)
547
548 dd_cmd = "dd if=/dev/zero of=%s bs=1M seek=%d count=0" % \
549 (fs, self.size)
550 exec_cmd(dd_cmd)
551
552 mkfs_cmd = "mkfs.%s -b %d %s" % (self.fstype, self.size * 1024, rootfs)
553 (rc, out) = exec_native_cmd(mkfs_cmd, native_sysroot)
554 if rc:
555 msger.error("ERROR: mkfs.%s returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details)" % (self.fstype, rc))
556
557 mkfs_cmd = "mkfs.%s -F %s %s" % (self.fstype, extra_imagecmd, fs)
558 (rc, out) = exec_native_cmd(mkfs_cmd, native_sysroot)
559 if rc:
560 msger.error("ERROR: mkfs.%s returned '%s' instead of 0 (which you probably don't want to ignore, use --debug for details)" % (self.fstype, rc))
561
562 self.source_file = fs
563
564 return 0
565
566 def prepare_empty_partition_vfat(self, cr_workdir, oe_builddir,
567 native_sysroot):
568 """
569 Prepare an empty vfat partition.
570 """
571 fs = "%s/fs_%s.%s" % (cr_workdir, self.label, self.fstype)
572
573 blocks = self.size * 1024
574
575 dosfs_cmd = "mkdosfs -n boot -S 512 -C %s %d" % (fs, blocks)
576 exec_native_cmd(dosfs_cmd, native_sysroot)
577
578 chmod_cmd = "chmod 644 %s" % fs
579 exec_cmd(chmod_cmd)
580
581 self.source_file = fs
582
583 return 0
584
585 def prepare_empty_partition_squashfs(self, cr_workdir, oe_builddir,
586 native_sysroot):
587 """
588 Prepare an empty squashfs partition.
589 """
590 msger.warning("Creating of an empty squashfs %s partition was attempted. " \
591 "Proceeding as requested." % self.mountpoint)
592
593 fs = "%s/fs_%s.%s" % (cr_workdir, self.label, self.fstype)
594
595 # it is not possible to create a squashfs without source data,
596 # thus prepare an empty temp dir that is used as source
597 tmpdir = tempfile.mkdtemp()
598
599 squashfs_cmd = "mksquashfs %s %s -noappend" % \
600 (tmpdir, fs)
601 exec_native_cmd(squashfs_cmd, native_sysroot)
602
603 os.rmdir(tmpdir)
604
605 # get the rootfs size in the right units for kickstart (Mb)
606 du_cmd = "du -Lbms %s" % fs
607 out = exec_cmd(du_cmd)
608 fs_size = out.split()[0]
609
610 self.size = fs_size
611 self.source_file = fs
612
613 return 0
614
615 def prepare_swap_partition(self, cr_workdir, oe_builddir, native_sysroot):
616 """
617 Prepare a swap partition.
618 """
619 fs = "%s/fs.%s" % (cr_workdir, self.fstype)
620
621 dd_cmd = "dd if=/dev/zero of=%s bs=1M seek=%d count=0" % \
622 (fs, self.size)
623 exec_cmd(dd_cmd)
624
625 import uuid
626 label_str = ""
627 if self.label:
628 label_str = "-L %s" % self.label
629 mkswap_cmd = "mkswap %s -U %s %s" % (label_str, str(uuid.uuid1()), fs)
630 exec_native_cmd(mkswap_cmd, native_sysroot)
631
632 self.source_file = fs
633
634 return 0
635
636class Wic_Partition(Mic_Partition):
637 removedKeywords = Mic_Partition.removedKeywords
638 removedAttrs = Mic_Partition.removedAttrs
639
640 def _getParser(self):
641 op = Mic_Partition._getParser(self)
642 # use specified source file to fill the partition
643 # and calculate partition size
644 op.add_option("--source", type="string", action="store",
645 dest="source", default=None)
646 # comma-separated list of param=value pairs
647 op.add_option("--sourceparams", type="string", action="store",
648 dest="sourceparams", default=None)
649 # use specified rootfs path to fill the partition
650 op.add_option("--rootfs-dir", type="string", action="store",
651 dest="rootfs", default=None)
652 return op
diff --git a/scripts/lib/wic/kickstart/custom_commands/wicboot.py b/scripts/lib/wic/kickstart/custom_commands/wicboot.py
new file mode 100644
index 0000000000..f1914169d8
--- /dev/null
+++ b/scripts/lib/wic/kickstart/custom_commands/wicboot.py
@@ -0,0 +1,57 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This module provides the OpenEmbedded bootloader object definitions.
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27from pykickstart.base import *
28from pykickstart.errors import *
29from pykickstart.options import *
30from pykickstart.commands.bootloader import *
31
32from wic.kickstart.custom_commands.micboot import *
33
34class Wic_Bootloader(Mic_Bootloader):
35 def __init__(self, writePriority=10, appendLine="", driveorder=None,
36 forceLBA=False, location="", md5pass="", password="",
37 upgrade=False, menus=""):
38 Mic_Bootloader.__init__(self, writePriority, appendLine, driveorder,
39 forceLBA, location, md5pass, password, upgrade)
40
41 self.source = ""
42
43 def _getArgsAsStr(self):
44 retval = Mic_Bootloader._getArgsAsStr(self)
45
46 if self.source:
47 retval += " --source=%s" % self.source
48
49 return retval
50
51 def _getParser(self):
52 op = Mic_Bootloader._getParser(self)
53 # use specified source plugin to implement bootloader-specific methods
54 op.add_option("--source", type="string", action="store",
55 dest="source", default=None)
56 return op
57
diff --git a/scripts/lib/wic/msger.py b/scripts/lib/wic/msger.py
new file mode 100644
index 0000000000..9f557e7b9a
--- /dev/null
+++ b/scripts/lib/wic/msger.py
@@ -0,0 +1,309 @@
1#!/usr/bin/env python -tt
2# vim: ai ts=4 sts=4 et sw=4
3#
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19import os,sys
20import re
21import time
22
23__ALL__ = ['set_mode',
24 'get_loglevel',
25 'set_loglevel',
26 'set_logfile',
27 'raw',
28 'debug',
29 'verbose',
30 'info',
31 'warning',
32 'error',
33 'ask',
34 'pause',
35 ]
36
37# COLORs in ANSI
38INFO_COLOR = 32 # green
39WARN_COLOR = 33 # yellow
40ERR_COLOR = 31 # red
41ASK_COLOR = 34 # blue
42NO_COLOR = 0
43
44PREFIX_RE = re.compile('^<(.*?)>\s*(.*)', re.S)
45
46INTERACTIVE = True
47
48LOG_LEVEL = 1
49LOG_LEVELS = {
50 'quiet': 0,
51 'normal': 1,
52 'verbose': 2,
53 'debug': 3,
54 'never': 4,
55 }
56
57LOG_FILE_FP = None
58LOG_CONTENT = ''
59CATCHERR_BUFFILE_FD = -1
60CATCHERR_BUFFILE_PATH = None
61CATCHERR_SAVED_2 = -1
62
63def _general_print(head, color, msg = None, stream = None, level = 'normal'):
64 global LOG_CONTENT
65 if not stream:
66 stream = sys.stdout
67
68 if LOG_LEVELS[level] > LOG_LEVEL:
69 # skip
70 return
71
72 # encode raw 'unicode' str to utf8 encoded str
73 if msg and isinstance(msg, unicode):
74 msg = msg.encode('utf-8', 'ignore')
75
76 errormsg = ''
77 if CATCHERR_BUFFILE_FD > 0:
78 size = os.lseek(CATCHERR_BUFFILE_FD , 0, os.SEEK_END)
79 os.lseek(CATCHERR_BUFFILE_FD, 0, os.SEEK_SET)
80 errormsg = os.read(CATCHERR_BUFFILE_FD, size)
81 os.ftruncate(CATCHERR_BUFFILE_FD, 0)
82
83 # append error msg to LOG
84 if errormsg:
85 LOG_CONTENT += errormsg
86
87 # append normal msg to LOG
88 save_msg = msg.strip() if msg else None
89 if save_msg:
90 timestr = time.strftime("[%m/%d %H:%M:%S %Z] ", time.localtime())
91 LOG_CONTENT += timestr + save_msg + '\n'
92
93 if errormsg:
94 _color_print('', NO_COLOR, errormsg, stream, level)
95
96 _color_print(head, color, msg, stream, level)
97
98def _color_print(head, color, msg, stream, level):
99 colored = True
100 if color == NO_COLOR or \
101 not stream.isatty() or \
102 os.getenv('ANSI_COLORS_DISABLED') is not None:
103 colored = False
104
105 if head.startswith('\r'):
106 # need not \n at last
107 newline = False
108 else:
109 newline = True
110
111 if colored:
112 head = '\033[%dm%s:\033[0m ' %(color, head)
113 if not newline:
114 # ESC cmd to clear line
115 head = '\033[2K' + head
116 else:
117 if head:
118 head += ': '
119 if head.startswith('\r'):
120 head = head.lstrip()
121 newline = True
122
123 if msg is not None:
124 if isinstance(msg, unicode):
125 msg = msg.encode('utf8', 'ignore')
126
127 stream.write('%s%s' % (head, msg))
128 if newline:
129 stream.write('\n')
130
131 stream.flush()
132
133def _color_perror(head, color, msg, level = 'normal'):
134 if CATCHERR_BUFFILE_FD > 0:
135 _general_print(head, color, msg, sys.stdout, level)
136 else:
137 _general_print(head, color, msg, sys.stderr, level)
138
139def _split_msg(head, msg):
140 if isinstance(msg, list):
141 msg = '\n'.join(map(str, msg))
142
143 if msg.startswith('\n'):
144 # means print \n at first
145 msg = msg.lstrip()
146 head = '\n' + head
147
148 elif msg.startswith('\r'):
149 # means print \r at first
150 msg = msg.lstrip()
151 head = '\r' + head
152
153 m = PREFIX_RE.match(msg)
154 if m:
155 head += ' <%s>' % m.group(1)
156 msg = m.group(2)
157
158 return head, msg
159
160def get_loglevel():
161 return (k for k,v in LOG_LEVELS.items() if v==LOG_LEVEL).next()
162
163def set_loglevel(level):
164 global LOG_LEVEL
165 if level not in LOG_LEVELS:
166 # no effect
167 return
168
169 LOG_LEVEL = LOG_LEVELS[level]
170
171def set_interactive(mode=True):
172 global INTERACTIVE
173 if mode:
174 INTERACTIVE = True
175 else:
176 INTERACTIVE = False
177
178def log(msg=''):
179 # log msg to LOG_CONTENT then save to logfile
180 global LOG_CONTENT
181 if msg:
182 LOG_CONTENT += msg
183
184def raw(msg=''):
185 _general_print('', NO_COLOR, msg)
186
187def info(msg):
188 head, msg = _split_msg('Info', msg)
189 _general_print(head, INFO_COLOR, msg)
190
191def verbose(msg):
192 head, msg = _split_msg('Verbose', msg)
193 _general_print(head, INFO_COLOR, msg, level = 'verbose')
194
195def warning(msg):
196 head, msg = _split_msg('Warning', msg)
197 _color_perror(head, WARN_COLOR, msg)
198
199def debug(msg):
200 head, msg = _split_msg('Debug', msg)
201 _color_perror(head, ERR_COLOR, msg, level = 'debug')
202
203def error(msg):
204 head, msg = _split_msg('Error', msg)
205 _color_perror(head, ERR_COLOR, msg)
206 sys.exit(1)
207
208def ask(msg, default=True):
209 _general_print('\rQ', ASK_COLOR, '')
210 try:
211 if default:
212 msg += '(Y/n) '
213 else:
214 msg += '(y/N) '
215 if INTERACTIVE:
216 while True:
217 repl = raw_input(msg)
218 if repl.lower() == 'y':
219 return True
220 elif repl.lower() == 'n':
221 return False
222 elif not repl.strip():
223 # <Enter>
224 return default
225
226 # else loop
227 else:
228 if default:
229 msg += ' Y'
230 else:
231 msg += ' N'
232 _general_print('', NO_COLOR, msg)
233
234 return default
235 except KeyboardInterrupt:
236 sys.stdout.write('\n')
237 sys.exit(2)
238
239def choice(msg, choices, default=0):
240 if default >= len(choices):
241 return None
242 _general_print('\rQ', ASK_COLOR, '')
243 try:
244 msg += " [%s] " % '/'.join(choices)
245 if INTERACTIVE:
246 while True:
247 repl = raw_input(msg)
248 if repl in choices:
249 return repl
250 elif not repl.strip():
251 return choices[default]
252 else:
253 msg += choices[default]
254 _general_print('', NO_COLOR, msg)
255
256 return choices[default]
257 except KeyboardInterrupt:
258 sys.stdout.write('\n')
259 sys.exit(2)
260
261def pause(msg=None):
262 if INTERACTIVE:
263 _general_print('\rQ', ASK_COLOR, '')
264 if msg is None:
265 msg = 'press <ENTER> to continue ...'
266 raw_input(msg)
267
268def set_logfile(fpath):
269 global LOG_FILE_FP
270
271 def _savelogf():
272 if LOG_FILE_FP:
273 fp = open(LOG_FILE_FP, 'w')
274 fp.write(LOG_CONTENT)
275 fp.close()
276
277 if LOG_FILE_FP is not None:
278 warning('duplicate log file configuration')
279
280 LOG_FILE_FP = fpath
281
282 import atexit
283 atexit.register(_savelogf)
284
285def enable_logstderr(fpath):
286 global CATCHERR_BUFFILE_FD
287 global CATCHERR_BUFFILE_PATH
288 global CATCHERR_SAVED_2
289
290 if os.path.exists(fpath):
291 os.remove(fpath)
292 CATCHERR_BUFFILE_PATH = fpath
293 CATCHERR_BUFFILE_FD = os.open(CATCHERR_BUFFILE_PATH, os.O_RDWR|os.O_CREAT)
294 CATCHERR_SAVED_2 = os.dup(2)
295 os.dup2(CATCHERR_BUFFILE_FD, 2)
296
297def disable_logstderr():
298 global CATCHERR_BUFFILE_FD
299 global CATCHERR_BUFFILE_PATH
300 global CATCHERR_SAVED_2
301
302 raw(msg = None) # flush message buffer and print it.
303 os.dup2(CATCHERR_SAVED_2, 2)
304 os.close(CATCHERR_SAVED_2)
305 os.close(CATCHERR_BUFFILE_FD)
306 os.unlink(CATCHERR_BUFFILE_PATH)
307 CATCHERR_BUFFILE_FD = -1
308 CATCHERR_BUFFILE_PATH = None
309 CATCHERR_SAVED_2 = -1
diff --git a/scripts/lib/wic/plugin.py b/scripts/lib/wic/plugin.py
new file mode 100644
index 0000000000..41a80175ca
--- /dev/null
+++ b/scripts/lib/wic/plugin.py
@@ -0,0 +1,156 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os, sys
19
20from wic import msger
21from wic import pluginbase
22from wic.utils import errors
23from wic.utils.oe.misc import *
24
25__ALL__ = ['PluginMgr', 'pluginmgr']
26
27PLUGIN_TYPES = ["imager", "source"]
28
29PLUGIN_DIR = "/lib/wic/plugins" # relative to scripts
30SCRIPTS_PLUGIN_DIR = "scripts" + PLUGIN_DIR
31
32class PluginMgr(object):
33 plugin_dirs = {}
34
35 # make the manager class as singleton
36 _instance = None
37 def __new__(cls, *args, **kwargs):
38 if not cls._instance:
39 cls._instance = super(PluginMgr, cls).__new__(cls, *args, **kwargs)
40
41 return cls._instance
42
43 def __init__(self):
44 wic_path = os.path.dirname(__file__)
45 eos = wic_path.find('scripts') + len('scripts')
46 scripts_path = wic_path[:eos]
47 self.scripts_path = scripts_path
48 self.plugin_dir = scripts_path + PLUGIN_DIR
49 self.layers_path = None
50
51 def _build_plugin_dir_list(self, dl, ptype):
52 if self.layers_path is None:
53 self.layers_path = get_bitbake_var("BBLAYERS")
54 layer_dirs = []
55
56 if self.layers_path is not None:
57 for layer_path in self.layers_path.split():
58 path = os.path.join(layer_path, SCRIPTS_PLUGIN_DIR, ptype)
59 layer_dirs.append(path)
60
61 path = os.path.join(dl, ptype)
62 layer_dirs.append(path)
63
64 return layer_dirs
65
66 def append_dirs(self, dirs):
67 for path in dirs:
68 self._add_plugindir(path)
69
70 # load all the plugins AGAIN
71 self._load_all()
72
73 def _add_plugindir(self, path):
74 path = os.path.abspath(os.path.expanduser(path))
75
76 if not os.path.isdir(path):
77 msger.debug("Plugin dir is not a directory or does not exist: %s"\
78 % path)
79 return
80
81 if path not in self.plugin_dirs:
82 self.plugin_dirs[path] = False
83 # the value True/False means "loaded"
84
85 def _load_all(self):
86 for (pdir, loaded) in self.plugin_dirs.iteritems():
87 if loaded: continue
88
89 sys.path.insert(0, pdir)
90 for mod in [x[:-3] for x in os.listdir(pdir) if x.endswith(".py")]:
91 if mod and mod != '__init__':
92 if mod in sys.modules:
93 #self.plugin_dirs[pdir] = True
94 msger.warning("Module %s already exists, skip" % mod)
95 else:
96 try:
97 pymod = __import__(mod)
98 self.plugin_dirs[pdir] = True
99 msger.debug("Plugin module %s:%s imported"\
100 % (mod, pymod.__file__))
101 except ImportError, err:
102 msg = 'Failed to load plugin %s/%s: %s' \
103 % (os.path.basename(pdir), mod, err)
104 msger.warning(msg)
105
106 del(sys.path[0])
107
108 def get_plugins(self, ptype):
109 """ the return value is dict of name:class pairs """
110
111 if ptype not in PLUGIN_TYPES:
112 raise errors.CreatorError('%s is not valid plugin type' % ptype)
113
114 plugins_dir = self._build_plugin_dir_list(self.plugin_dir, ptype)
115
116 self.append_dirs(plugins_dir)
117
118 return pluginbase.get_plugins(ptype)
119
120 def get_source_plugins(self):
121 """
122 Return list of available source plugins.
123 """
124 plugins_dir = self._build_plugin_dir_list(self.plugin_dir, 'source')
125
126 self.append_dirs(plugins_dir)
127
128 plugins = []
129
130 for _source_name, klass in self.get_plugins('source').iteritems():
131 plugins.append(_source_name)
132
133 return plugins
134
135
136 def get_source_plugin_methods(self, source_name, methods):
137 """
138 The methods param is a dict with the method names to find. On
139 return, the dict values will be filled in with pointers to the
140 corresponding methods. If one or more methods are not found,
141 None is returned.
142 """
143 return_methods = None
144 for _source_name, klass in self.get_plugins('source').iteritems():
145 if _source_name == source_name:
146 for _method_name in methods.keys():
147 if not hasattr(klass, _method_name):
148 msger.warning("Unimplemented %s source interface for: %s"\
149 % (_method_name, _source_name))
150 return None
151 func = getattr(klass, _method_name)
152 methods[_method_name] = func
153 return_methods = methods
154 return return_methods
155
156pluginmgr = PluginMgr()
diff --git a/scripts/lib/wic/pluginbase.py b/scripts/lib/wic/pluginbase.py
new file mode 100644
index 0000000000..d63e34e089
--- /dev/null
+++ b/scripts/lib/wic/pluginbase.py
@@ -0,0 +1,120 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import shutil
20from wic import msger
21from wic.utils import errors
22
23class _Plugin(object):
24 class __metaclass__(type):
25 def __init__(cls, name, bases, attrs):
26 if not hasattr(cls, 'plugins'):
27 cls.plugins = {}
28
29 elif 'wic_plugin_type' in attrs:
30 if attrs['wic_plugin_type'] not in cls.plugins:
31 cls.plugins[attrs['wic_plugin_type']] = {}
32
33 elif hasattr(cls, 'wic_plugin_type') and 'name' in attrs:
34 cls.plugins[cls.wic_plugin_type][attrs['name']] = cls
35
36 def show_plugins(cls):
37 for cls in cls.plugins[cls.wic_plugin_type]:
38 print cls
39
40 def get_plugins(cls):
41 return cls.plugins
42
43
44class ImagerPlugin(_Plugin):
45 wic_plugin_type = "imager"
46
47
48class SourcePlugin(_Plugin):
49 wic_plugin_type = "source"
50 """
51 The methods that can be implemented by --source plugins.
52
53 Any methods not implemented in a subclass inherit these.
54 """
55
56 @classmethod
57 def do_install_pkgs(self, part, creator, cr_workdir, oe_builddir, rootfs_dir,
58 bootimg_dir, kernel_dir, native_sysroot):
59 """
60 Called before partitions have been prepared and assembled into a
61 disk image. Install packages into rootfs
62 """
63 msger.debug("SourcePlugin: do_install_pkgs: part %s" % part)
64
65 @classmethod
66 def do_install_disk(self, disk, disk_name, cr, workdir, oe_builddir,
67 bootimg_dir, kernel_dir, native_sysroot):
68 """
69 Called after all partitions have been prepared and assembled into a
70 disk image. This provides a hook to allow finalization of a
71 disk image e.g. to write an MBR to it.
72 """
73 msger.debug("SourcePlugin: do_install_disk: disk: %s" % disk_name)
74
75 @classmethod
76 def do_stage_partition(self, part, source_params, cr, cr_workdir,
77 oe_builddir, bootimg_dir, kernel_dir,
78 native_sysroot):
79 """
80 Special content staging hook called before do_prepare_partition(),
81 normally empty.
82
83 Typically, a partition will just use the passed-in parame e.g
84 straight bootimg_dir, etc, but in some cases, things need to
85 be more tailored e.g. to use a deploy dir + /boot, etc. This
86 hook allows those files to be staged in a customized fashion.
87 Not that get_bitbake_var() allows you to acces non-standard
88 variables that you might want to use for this.
89 """
90 msger.debug("SourcePlugin: do_stage_partition: part: %s" % part)
91
92 @classmethod
93 def do_configure_partition(self, part, source_params, cr, cr_workdir,
94 oe_builddir, bootimg_dir, kernel_dir,
95 native_sysroot):
96 """
97 Called before do_prepare_partition(), typically used to create
98 custom configuration files for a partition, for example
99 syslinux or grub config files.
100 """
101 msger.debug("SourcePlugin: do_configure_partition: part: %s" % part)
102
103 @classmethod
104 def do_prepare_partition(self, part, source_params, cr, cr_workdir,
105 oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
106 native_sysroot):
107 """
108 Called to do the actual content population for a partition i.e. it
109 'prepares' the partition to be incorporated into the image.
110 """
111 msger.debug("SourcePlugin: do_prepare_partition: part: %s" % part)
112
113def get_plugins(typen):
114 ps = ImagerPlugin.get_plugins()
115 if typen in ps:
116 return ps[typen]
117 else:
118 return None
119
120__all__ = ['ImagerPlugin', 'SourcePlugin', 'get_plugins']
diff --git a/scripts/lib/wic/plugins/imager/direct_plugin.py b/scripts/lib/wic/plugins/imager/direct_plugin.py
new file mode 100644
index 0000000000..5601c3f1c9
--- /dev/null
+++ b/scripts/lib/wic/plugins/imager/direct_plugin.py
@@ -0,0 +1,98 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'direct' imager plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import os
28import shutil
29import re
30import tempfile
31
32from wic import msger
33from wic.utils import misc, fs_related, errors, runner, cmdln
34from wic.conf import configmgr
35from wic.plugin import pluginmgr
36
37import wic.imager.direct as direct
38from wic.pluginbase import ImagerPlugin
39
40class DirectPlugin(ImagerPlugin):
41 name = 'direct'
42
43 @classmethod
44 def __rootfs_dir_to_dict(self, rootfs_dirs):
45 """
46 Gets a string that contain 'connection=dir' splitted by
47 space and return a dict
48 """
49 krootfs_dir = {}
50 for rootfs_dir in rootfs_dirs.split(' '):
51 k, v = rootfs_dir.split('=')
52 krootfs_dir[k] = v
53
54 return krootfs_dir
55
56 @classmethod
57 def do_create(self, subcmd, opts, *args):
58 """
59 Create direct image, called from creator as 'direct' cmd
60 """
61 if len(args) != 7:
62 raise errors.Usage("Extra arguments given")
63
64 native_sysroot = args[0]
65 kernel_dir = args[1]
66 bootimg_dir = args[2]
67 rootfs_dir = args[3]
68
69 creatoropts = configmgr.create
70 ksconf = args[4]
71
72 image_output_dir = args[5]
73 oe_builddir = args[6]
74
75 krootfs_dir = self.__rootfs_dir_to_dict(rootfs_dir)
76
77 configmgr._ksconf = ksconf
78
79 creator = direct.DirectImageCreator(oe_builddir,
80 image_output_dir,
81 krootfs_dir,
82 bootimg_dir,
83 kernel_dir,
84 native_sysroot,
85 creatoropts)
86
87 try:
88 creator.create()
89 creator.assemble()
90 creator.finalize()
91 creator.print_outimage_info()
92
93 except errors.CreatorError:
94 raise
95 finally:
96 creator.cleanup()
97
98 return 0
diff --git a/scripts/lib/wic/plugins/source/bootimg-efi.py b/scripts/lib/wic/plugins/source/bootimg-efi.py
new file mode 100644
index 0000000000..e4067b6dbf
--- /dev/null
+++ b/scripts/lib/wic/plugins/source/bootimg-efi.py
@@ -0,0 +1,236 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'bootimg-efi' source plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import os
28import shutil
29import re
30import tempfile
31
32from wic import kickstart, msger
33from wic.utils import misc, fs_related, errors, runner, cmdln
34from wic.conf import configmgr
35from wic.plugin import pluginmgr
36import wic.imager.direct as direct
37from wic.pluginbase import SourcePlugin
38from wic.utils.oe.misc import *
39from wic.imager.direct import DirectImageCreator
40
41class BootimgEFIPlugin(SourcePlugin):
42 name = 'bootimg-efi'
43
44 @classmethod
45 def do_configure_grubefi(self, hdddir, cr, cr_workdir):
46 """
47 Create loader-specific (grub-efi) config
48 """
49 splash = os.path.join(cr_workdir, "/EFI/boot/splash.jpg")
50 if os.path.exists(splash):
51 splashline = "menu background splash.jpg"
52 else:
53 splashline = ""
54
55 (rootdev, root_part_uuid) = cr._get_boot_config()
56 options = cr.ks.handler.bootloader.appendLine
57
58 grubefi_conf = ""
59 grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
60 grubefi_conf += "default=boot\n"
61 timeout = kickstart.get_timeout(cr.ks)
62 if not timeout:
63 timeout = 0
64 grubefi_conf += "timeout=%s\n" % timeout
65 grubefi_conf += "menuentry 'boot'{\n"
66
67 kernel = "/bzImage"
68
69 if cr._ptable_format == 'msdos':
70 rootstr = rootdev
71 else:
72 raise ImageError("Unsupported partition table format found")
73
74 grubefi_conf += "linux %s root=%s rootwait %s\n" \
75 % (kernel, rootstr, options)
76 grubefi_conf += "}\n"
77 if splashline:
78 syslinux_conf += "%s\n" % splashline
79
80 msger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg" \
81 % cr_workdir)
82 cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
83 cfg.write(grubefi_conf)
84 cfg.close()
85
86 @classmethod
87 def do_configure_gummiboot(self, hdddir, cr, cr_workdir):
88 """
89 Create loader-specific (gummiboot) config
90 """
91 install_cmd = "install -d %s/loader" % hdddir
92 exec_cmd(install_cmd)
93
94 install_cmd = "install -d %s/loader/entries" % hdddir
95 exec_cmd(install_cmd)
96
97 (rootdev, root_part_uuid) = cr._get_boot_config()
98 options = cr.ks.handler.bootloader.appendLine
99
100 timeout = kickstart.get_timeout(cr.ks)
101 if not timeout:
102 timeout = 0
103
104 loader_conf = ""
105 loader_conf += "default boot\n"
106 loader_conf += "timeout %d\n" % timeout
107
108 msger.debug("Writing gummiboot config %s/hdd/boot/loader/loader.conf" \
109 % cr_workdir)
110 cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
111 cfg.write(loader_conf)
112 cfg.close()
113
114 kernel = "/bzImage"
115
116 if cr._ptable_format == 'msdos':
117 rootstr = rootdev
118 else:
119 raise ImageError("Unsupported partition table format found")
120
121 boot_conf = ""
122 boot_conf += "title boot\n"
123 boot_conf += "linux %s\n" % kernel
124 boot_conf += "options LABEL=Boot root=%s %s\n" \
125 % (rootstr, options)
126
127 msger.debug("Writing gummiboot config %s/hdd/boot/loader/entries/boot.conf" \
128 % cr_workdir)
129 cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
130 cfg.write(boot_conf)
131 cfg.close()
132
133
134 @classmethod
135 def do_configure_partition(self, part, source_params, cr, cr_workdir,
136 oe_builddir, bootimg_dir, kernel_dir,
137 native_sysroot):
138 """
139 Called before do_prepare_partition(), creates loader-specific config
140 """
141 hdddir = "%s/hdd/boot" % cr_workdir
142 rm_cmd = "rm -rf %s" % cr_workdir
143 exec_cmd(rm_cmd)
144
145 install_cmd = "install -d %s/EFI/BOOT" % hdddir
146 exec_cmd(install_cmd)
147
148 try:
149 if source_params['loader'] == 'grub-efi':
150 self.do_configure_grubefi(hdddir, cr, cr_workdir)
151 elif source_params['loader'] == 'gummiboot':
152 self.do_configure_gummiboot(hdddir, cr, cr_workdir)
153 else:
154 msger.error("unrecognized bootimg-efi loader: %s" % source_params['loader'])
155 except KeyError:
156 msger.error("bootimg-efi requires a loader, none specified")
157
158
159 @classmethod
160 def do_prepare_partition(self, part, source_params, cr, cr_workdir,
161 oe_builddir, bootimg_dir, kernel_dir,
162 rootfs_dir, native_sysroot):
163 """
164 Called to do the actual content population for a partition i.e. it
165 'prepares' the partition to be incorporated into the image.
166 In this case, prepare content for an EFI (grub) boot partition.
167 """
168 if not bootimg_dir:
169 bootimg_dir = get_bitbake_var("HDDDIR")
170 if not bootimg_dir:
171 msger.error("Couldn't find HDDDIR, exiting\n")
172 # just so the result notes display it
173 cr.set_bootimg_dir(bootimg_dir)
174
175 staging_kernel_dir = kernel_dir
176
177 hdddir = "%s/hdd/boot" % cr_workdir
178
179 install_cmd = "install -m 0644 %s/bzImage %s/bzImage" % \
180 (staging_kernel_dir, hdddir)
181 exec_cmd(install_cmd)
182
183 try:
184 if source_params['loader'] == 'grub-efi':
185 shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
186 "%s/grub.cfg" % cr_workdir)
187 cp_cmd = "cp %s/EFI/BOOT/* %s/EFI/BOOT" % (bootimg_dir, hdddir)
188 exec_cmd(cp_cmd, True)
189 shutil.move("%s/grub.cfg" % cr_workdir,
190 "%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
191 elif source_params['loader'] == 'gummiboot':
192 cp_cmd = "cp %s/EFI/BOOT/* %s/EFI/BOOT" % (bootimg_dir, hdddir)
193 exec_cmd(cp_cmd, True)
194 else:
195 msger.error("unrecognized bootimg-efi loader: %s" % source_params['loader'])
196 except KeyError:
197 msger.error("bootimg-efi requires a loader, none specified")
198
199 du_cmd = "du -bks %s" % hdddir
200 out = exec_cmd(du_cmd)
201 blocks = int(out.split()[0])
202
203 extra_blocks = part.get_extra_block_count(blocks)
204
205 if extra_blocks < BOOTDD_EXTRA_SPACE:
206 extra_blocks = BOOTDD_EXTRA_SPACE
207
208 blocks += extra_blocks
209
210 msger.debug("Added %d extra blocks to %s to get to %d total blocks" % \
211 (extra_blocks, part.mountpoint, blocks))
212
213 # Ensure total sectors is an integral number of sectors per
214 # track or mcopy will complain. Sectors are 512 bytes, and we
215 # generate images with 32 sectors per track. This calculation is
216 # done in blocks, thus the mod by 16 instead of 32.
217 blocks += (16 - (blocks % 16))
218
219 # dosfs image, created by mkdosfs
220 bootimg = "%s/boot.img" % cr_workdir
221
222 dosfs_cmd = "mkdosfs -n efi -C %s %d" % (bootimg, blocks)
223 exec_native_cmd(dosfs_cmd, native_sysroot)
224
225 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
226 exec_native_cmd(mcopy_cmd, native_sysroot)
227
228 chmod_cmd = "chmod 644 %s" % bootimg
229 exec_cmd(chmod_cmd)
230
231 du_cmd = "du -Lbms %s" % bootimg
232 out = exec_cmd(du_cmd)
233 bootimg_size = out.split()[0]
234
235 part.set_size(bootimg_size)
236 part.set_source_file(bootimg)
diff --git a/scripts/lib/wic/plugins/source/bootimg-partition.py b/scripts/lib/wic/plugins/source/bootimg-partition.py
new file mode 100644
index 0000000000..564118ad8b
--- /dev/null
+++ b/scripts/lib/wic/plugins/source/bootimg-partition.py
@@ -0,0 +1,115 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License version 2 as
6# published by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16#
17# DESCRIPTION
18# This implements the 'bootimg-partition' source plugin class for
19# 'wic'. The plugin creates an image of boot partition, copying over
20# files listed in IMAGE_BOOT_FILES bitbake variable.
21#
22# AUTHORS
23# Maciej Borzecki <maciej.borzecki (at] open-rnd.pl>
24#
25
26import os
27import re
28
29from wic import msger
30from wic.pluginbase import SourcePlugin
31from wic.utils.oe.misc import *
32
33class BootimgPartitionPlugin(SourcePlugin):
34 name = 'bootimg-partition'
35
36 @classmethod
37 def do_install_disk(self, disk, disk_name, cr, workdir, oe_builddir,
38 bootimg_dir, kernel_dir, native_sysroot):
39 """
40 Called after all partitions have been prepared and assembled into a
41 disk image. Do nothing.
42 """
43 pass
44
45 @classmethod
46 def do_configure_partition(self, part, source_params, cr, cr_workdir,
47 oe_builddir, bootimg_dir, kernel_dir,
48 native_sysroot):
49 """
50 Called before do_prepare_partition(). Possibly prepare
51 configuration files of some sort.
52
53 """
54 pass
55
56 @classmethod
57 def do_prepare_partition(self, part, source_params, cr, cr_workdir,
58 oe_builddir, bootimg_dir, kernel_dir,
59 rootfs_dir, native_sysroot):
60 """
61 Called to do the actual content population for a partition i.e. it
62 'prepares' the partition to be incorporated into the image.
63 In this case, does the following:
64 - sets up a vfat partition
65 - copies all files listed in IMAGE_BOOT_FILES variable
66 """
67 hdddir = "%s/boot" % cr_workdir
68 rm_cmd = "rm -rf %s" % cr_workdir
69 exec_cmd(rm_cmd)
70
71 install_cmd = "install -d %s" % hdddir
72 exec_cmd(install_cmd)
73
74 if not bootimg_dir:
75 bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
76 if not bootimg_dir:
77 msger.error("Couldn't find DEPLOY_DIR_IMAGE, exiting\n")
78
79 msger.debug('Bootimg dir: %s' % bootimg_dir)
80
81 boot_files = get_bitbake_var("IMAGE_BOOT_FILES")
82
83 if not boot_files:
84 msger.error('No boot files defined, IMAGE_BOOT_FILES unset')
85
86 msger.debug('Boot files: %s' % boot_files)
87
88 # list of tuples (src_name, dst_name)
89 deploy_files = []
90 for src_entry in re.findall(r'[\w;\-\./]+', boot_files):
91 if ';' in src_entry:
92 dst_entry = tuple(src_entry.split(';'))
93 if not dst_entry[0] or not dst_entry[1]:
94 msger.error('Malformed boot file entry: %s' % (src_entry))
95 else:
96 dst_entry = (src_entry, src_entry)
97
98 msger.debug('Destination entry: %r' % (dst_entry,))
99 deploy_files.append(dst_entry)
100
101 for deploy_entry in deploy_files:
102 src, dst = deploy_entry
103 src_path = os.path.join(bootimg_dir, src)
104 dst_path = os.path.join(hdddir, dst)
105
106 msger.debug('Install %s as %s' % (os.path.basename(src_path),
107 dst_path))
108 install_cmd = "install -m 0644 -D %s %s" \
109 % (src_path, dst_path)
110 exec_cmd(install_cmd)
111
112 msger.debug('Prepare boot partition using rootfs in %s' % (hdddir))
113 part.prepare_rootfs(cr_workdir, oe_builddir, hdddir,
114 native_sysroot)
115
diff --git a/scripts/lib/wic/plugins/source/bootimg-pcbios.py b/scripts/lib/wic/plugins/source/bootimg-pcbios.py
new file mode 100644
index 0000000000..8a1aca1ad1
--- /dev/null
+++ b/scripts/lib/wic/plugins/source/bootimg-pcbios.py
@@ -0,0 +1,200 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'bootimg-pcbios' source plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import os
28import shutil
29import re
30import tempfile
31
32from wic import kickstart, msger
33from wic.utils import misc, fs_related, errors, runner, cmdln
34from wic.conf import configmgr
35from wic.plugin import pluginmgr
36import wic.imager.direct as direct
37from wic.pluginbase import SourcePlugin
38from wic.utils.oe.misc import *
39from wic.imager.direct import DirectImageCreator
40
41class BootimgPcbiosPlugin(SourcePlugin):
42 name = 'bootimg-pcbios'
43
44 @classmethod
45 def do_install_disk(self, disk, disk_name, cr, workdir, oe_builddir,
46 bootimg_dir, kernel_dir, native_sysroot):
47 """
48 Called after all partitions have been prepared and assembled into a
49 disk image. In this case, we install the MBR.
50 """
51 mbrfile = "%s/syslinux/" % bootimg_dir
52 if cr._ptable_format == 'msdos':
53 mbrfile += "mbr.bin"
54
55 if not os.path.exists(mbrfile):
56 msger.error("Couldn't find %s. If using the -e option, do you have the right MACHINE set in local.conf? If not, is the bootimg_dir path correct?" % mbrfile)
57
58 full_path = cr._full_path(workdir, disk_name, "direct")
59 msger.debug("Installing MBR on disk %s as %s with size %s bytes" \
60 % (disk_name, full_path, disk['min_size']))
61
62 rc = runner.show(['dd', 'if=%s' % mbrfile,
63 'of=%s' % full_path, 'conv=notrunc'])
64 if rc != 0:
65 raise ImageError("Unable to set MBR to %s" % full_path)
66
67 @classmethod
68 def do_configure_partition(self, part, source_params, cr, cr_workdir,
69 oe_builddir, bootimg_dir, kernel_dir,
70 native_sysroot):
71 """
72 Called before do_prepare_partition(), creates syslinux config
73 """
74 hdddir = "%s/hdd/boot" % cr_workdir
75 rm_cmd = "rm -rf " + cr_workdir
76 exec_cmd(rm_cmd)
77
78 install_cmd = "install -d %s" % hdddir
79 exec_cmd(install_cmd)
80
81 splash = os.path.join(cr_workdir, "/hdd/boot/splash.jpg")
82 if os.path.exists(splash):
83 splashline = "menu background splash.jpg"
84 else:
85 splashline = ""
86
87 (rootdev, root_part_uuid) = cr._get_boot_config()
88 options = cr.ks.handler.bootloader.appendLine
89
90 syslinux_conf = ""
91 syslinux_conf += "PROMPT 0\n"
92 timeout = kickstart.get_timeout(cr.ks)
93 if not timeout:
94 timeout = 0
95 syslinux_conf += "TIMEOUT " + str(timeout) + "\n"
96 syslinux_conf += "\n"
97 syslinux_conf += "ALLOWOPTIONS 1\n"
98 syslinux_conf += "SERIAL 0 115200\n"
99 syslinux_conf += "\n"
100 if splashline:
101 syslinux_conf += "%s\n" % splashline
102 syslinux_conf += "DEFAULT boot\n"
103 syslinux_conf += "LABEL boot\n"
104
105 kernel = "/vmlinuz"
106 syslinux_conf += "KERNEL " + kernel + "\n"
107
108 if cr._ptable_format == 'msdos':
109 rootstr = rootdev
110 else:
111 raise ImageError("Unsupported partition table format found")
112
113 syslinux_conf += "APPEND label=boot root=%s %s\n" % (rootstr, options)
114
115 msger.debug("Writing syslinux config %s/hdd/boot/syslinux.cfg" \
116 % cr_workdir)
117 cfg = open("%s/hdd/boot/syslinux.cfg" % cr_workdir, "w")
118 cfg.write(syslinux_conf)
119 cfg.close()
120
121 @classmethod
122 def do_prepare_partition(self, part, source_params, cr, cr_workdir,
123 oe_builddir, bootimg_dir, kernel_dir,
124 rootfs_dir, native_sysroot):
125 """
126 Called to do the actual content population for a partition i.e. it
127 'prepares' the partition to be incorporated into the image.
128 In this case, prepare content for legacy bios boot partition.
129 """
130 def _has_syslinux(dir):
131 if dir:
132 syslinux = "%s/syslinux" % dir
133 if os.path.exists(syslinux):
134 return True
135 return False
136
137 if not _has_syslinux(bootimg_dir):
138 bootimg_dir = get_bitbake_var("STAGING_DATADIR")
139 if not bootimg_dir:
140 msger.error("Couldn't find STAGING_DATADIR, exiting\n")
141 if not _has_syslinux(bootimg_dir):
142 msger.error("Please build syslinux first\n")
143 # just so the result notes display it
144 cr.set_bootimg_dir(bootimg_dir)
145
146 staging_kernel_dir = kernel_dir
147
148 hdddir = "%s/hdd/boot" % cr_workdir
149
150 install_cmd = "install -m 0644 %s/bzImage %s/vmlinuz" \
151 % (staging_kernel_dir, hdddir)
152 exec_cmd(install_cmd)
153
154 install_cmd = "install -m 444 %s/syslinux/ldlinux.sys %s/ldlinux.sys" \
155 % (bootimg_dir, hdddir)
156 exec_cmd(install_cmd)
157
158 du_cmd = "du -bks %s" % hdddir
159 out = exec_cmd(du_cmd)
160 blocks = int(out.split()[0])
161
162 extra_blocks = part.get_extra_block_count(blocks)
163
164 if extra_blocks < BOOTDD_EXTRA_SPACE:
165 extra_blocks = BOOTDD_EXTRA_SPACE
166
167 blocks += extra_blocks
168
169 msger.debug("Added %d extra blocks to %s to get to %d total blocks" % \
170 (extra_blocks, part.mountpoint, blocks))
171
172 # Ensure total sectors is an integral number of sectors per
173 # track or mcopy will complain. Sectors are 512 bytes, and we
174 # generate images with 32 sectors per track. This calculation is
175 # done in blocks, thus the mod by 16 instead of 32.
176 blocks += (16 - (blocks % 16))
177
178 # dosfs image, created by mkdosfs
179 bootimg = "%s/boot.img" % cr_workdir
180
181 dosfs_cmd = "mkdosfs -n boot -S 512 -C %s %d" % (bootimg, blocks)
182 exec_native_cmd(dosfs_cmd, native_sysroot)
183
184 mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
185 exec_native_cmd(mcopy_cmd, native_sysroot)
186
187 syslinux_cmd = "syslinux %s" % bootimg
188 exec_native_cmd(syslinux_cmd, native_sysroot)
189
190 chmod_cmd = "chmod 644 %s" % bootimg
191 exec_cmd(chmod_cmd)
192
193 du_cmd = "du -Lbms %s" % bootimg
194 out = exec_cmd(du_cmd)
195 bootimg_size = out.split()[0]
196
197 part.set_size(bootimg_size)
198 part.set_source_file(bootimg)
199
200
diff --git a/scripts/lib/wic/plugins/source/rootfs.py b/scripts/lib/wic/plugins/source/rootfs.py
new file mode 100644
index 0000000000..a432a18705
--- /dev/null
+++ b/scripts/lib/wic/plugins/source/rootfs.py
@@ -0,0 +1,92 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'rootfs' source plugin class for 'wic'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25# Joao Henrique Ferreira de Freitas <joaohf (at] gmail.com>
26#
27
28import os
29import shutil
30import re
31import tempfile
32
33from wic import kickstart, msger
34from wic.utils import misc, fs_related, errors, runner, cmdln
35from wic.conf import configmgr
36from wic.plugin import pluginmgr
37import wic.imager.direct as direct
38from wic.pluginbase import SourcePlugin
39from wic.utils.oe.misc import *
40from wic.imager.direct import DirectImageCreator
41
42class RootfsPlugin(SourcePlugin):
43 name = 'rootfs'
44
45 @staticmethod
46 def __get_rootfs_dir(rootfs_dir):
47 if os.path.isdir(rootfs_dir):
48 return rootfs_dir
49
50 bitbake_env_lines = find_bitbake_env_lines(rootfs_dir)
51 if not bitbake_env_lines:
52 msg = "Couldn't get bitbake environment, exiting."
53 msger.error(msg)
54
55 image_rootfs_dir = find_artifact(bitbake_env_lines, "IMAGE_ROOTFS")
56 if not os.path.isdir(image_rootfs_dir):
57 msg = "No valid artifact IMAGE_ROOTFS from image named"
58 msg += " %s has been found at %s, exiting.\n" % \
59 (rootfs_dir, image_rootfs_dir)
60 msger.error(msg)
61
62 return image_rootfs_dir
63
64 @classmethod
65 def do_prepare_partition(self, part, source_params, cr, cr_workdir,
66 oe_builddir, bootimg_dir, kernel_dir,
67 krootfs_dir, native_sysroot):
68 """
69 Called to do the actual content population for a partition i.e. it
70 'prepares' the partition to be incorporated into the image.
71 In this case, prepare content for legacy bios boot partition.
72 """
73 if part.rootfs is None:
74 if not 'ROOTFS_DIR' in krootfs_dir:
75 msg = "Couldn't find --rootfs-dir, exiting"
76 msger.error(msg)
77 rootfs_dir = krootfs_dir['ROOTFS_DIR']
78 else:
79 if part.rootfs in krootfs_dir:
80 rootfs_dir = krootfs_dir[part.rootfs]
81 elif part.rootfs:
82 rootfs_dir = part.rootfs
83 else:
84 msg = "Couldn't find --rootfs-dir=%s connection"
85 msg += " or it is not a valid path, exiting"
86 msger.error(msg % part.rootfs)
87
88 real_rootfs_dir = self.__get_rootfs_dir(rootfs_dir)
89
90 part.set_rootfs(real_rootfs_dir)
91 part.prepare_rootfs(cr_workdir, oe_builddir, real_rootfs_dir, native_sysroot)
92
diff --git a/scripts/lib/wic/plugins/source/uboot.py b/scripts/lib/wic/plugins/source/uboot.py
new file mode 100644
index 0000000000..57cb3cf8fe
--- /dev/null
+++ b/scripts/lib/wic/plugins/source/uboot.py
@@ -0,0 +1,173 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Enea AB.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'uboot' source plugin class for 'wic'
22#
23# AUTHORS
24# Adrian Calianu <adrian.calianu (at] enea.com>
25#
26
27import os
28import shutil
29import re
30import tempfile
31
32from mic import kickstart, chroot, msger
33from mic.utils import misc, fs_related, errors, runner, cmdln
34from mic.conf import configmgr
35from mic.plugin import pluginmgr
36from mic.utils.partitionedfs import PartitionedMount
37import mic.imager.direct as direct
38from mic.pluginbase import SourcePlugin
39from mic.utils.oe.misc import *
40from mic.imager.direct import DirectImageCreator
41
42def create_local_rootfs(part, creator, cr_workdir, krootfs_dir, native_sysroot):
43 # In order to have a full control over rootfs we will make a local copy under workdir
44 # and change rootfs_dir to new location.
45 # In this way we can install more than one ROOTFS_DIRs and/or use
46 # an empty rootfs to install packages, so a rootfs could be generated only from pkgs
47 # TBD: create workdir/rootfs ; copy rootfs-> workdir/rootfs; set rootfs=workdir/rootfs
48
49 cr_workdir = os.path.abspath(cr_workdir)
50 new_rootfs_dir = "%s/rootfs_%s" % (cr_workdir, creator.name)
51
52 rootfs_exists = 1
53 if part.rootfs is None:
54 if not 'ROOTFS_DIR' in krootfs_dir:
55 msg = "Couldn't find --rootfs-dir, exiting, "
56 msger.info(msg)
57 rootfs_exists = 0
58 rootfs_dir = krootfs_dir['ROOTFS_DIR']
59 creator.rootfs_dir['ROOTFS_DIR'] = new_rootfs_dir
60 else:
61 if part.rootfs in krootfs_dir:
62 rootfs_dir = krootfs_dir[part.rootfs]
63 creator.rootfs_dir[part.rootfs] = new_rootfs_dir
64 elif os.path.isdir(part.rootfs):
65 rootfs_dir = part.rootfs
66 part.rootfs = new_rootfs_dir
67 else:
68 msg = "Couldn't find --rootfs-dir=%s connection"
69 msg += " or it is not a valid path, exiting"
70 msger.info(msg % part.rootfs)
71 rootfs_exists = 0
72 creator.rootfs_dir['ROOTFS_DIR'] = new_rootfs_dir
73
74 pseudox = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
75 pseudox += "export PSEUDO_LOCALSTATEDIR=%s/../pseudo;" % new_rootfs_dir
76 pseudox += "export PSEUDO_PASSWD=%s;" % new_rootfs_dir
77 pseudox += "export PSEUDO_NOSYMLINKEXP=1;"
78 pseudox += "%s/usr/bin/pseudo " % native_sysroot
79
80 mkdir_cmd = "mkdir %s" % (new_rootfs_dir)
81 # rc, out = exec_native_cmd(pseudox + mkdir_cmd, native_sysroot)
82 rc, out = exec_cmd(mkdir_cmd, True)
83
84 if rootfs_exists == 1 and os.path.isdir(rootfs_dir):
85 defpath = os.environ['PATH']
86 os.environ['PATH'] = native_sysroot + "/usr/bin/" + ":/bin:/usr/bin:"
87
88 rootfs_dir = os.path.abspath(rootfs_dir)
89
90 pseudoc = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
91 pseudoc += "export PSEUDO_LOCALSTATEDIR=%s/../pseudo;" % rootfs_dir
92 pseudoc += "export PSEUDO_PASSWD=%s;" % rootfs_dir
93 pseudoc += "export PSEUDO_NOSYMLINKEXP=1;"
94 pseudoc += "%s/usr/bin/pseudo " % native_sysroot
95
96 tarc_cmd = "tar cvpf %s/rootfs.tar -C %s ." % (cr_workdir, rootfs_dir)
97 rc, out = exec_native_cmd(pseudoc + tarc_cmd, native_sysroot)
98
99 tarx_cmd = "tar xpvf %s/rootfs.tar -C %s" % (cr_workdir, new_rootfs_dir)
100 rc, out = exec_native_cmd(pseudox + tarx_cmd, native_sysroot)
101
102 rm_cmd = "rm %s/rootfs.tar" % cr_workdir
103 rc, out = exec_cmd(rm_cmd, True)
104
105 os.environ['PATH'] += defpath + ":" + native_sysroot + "/usr/bin/"
106
107 return new_rootfs_dir
108
109class UBootPlugin(SourcePlugin):
110 name = 'uboot'
111
112 @classmethod
113 def do_install_pkgs(self, part, creator, cr_workdir, oe_builddir, krootfs_dir,
114 bootimg_dir, kernel_dir, native_sysroot):
115 """
116 Called before all partitions have been prepared and assembled into a
117 disk image. Intall packages based on wic configuration.
118 """
119
120 # set new rootfs_dir
121 rootfs_dir = create_local_rootfs(part, creator, cr_workdir, krootfs_dir, native_sysroot)
122
123 # wks file parsing
124 packages = kickstart.get_packages(creator.ks)
125
126 # wic.conf file parsing = found under 'creator'
127 local_pkgs_path = creator._local_pkgs_path
128 repourl = creator.repourl
129 pkgmgr = creator.pkgmgr_name
130
131 # install packages
132 if packages and pkgmgr in ["opkg"]:
133 if len(repourl) > 0 :
134 part.install_pkgs_ipk(cr_workdir, oe_builddir, rootfs_dir, native_sysroot,
135 packages, repourl)
136 else:
137 msger.error("No packages repository provided in wic.conf")
138
139 @classmethod
140 def do_prepare_partition(self, part, cr, cr_workdir, oe_builddir, bootimg_dir,
141 kernel_dir, krootfs_dir, native_sysroot):
142 """
143 Called to do the actual content population for a partition i.e. it
144 'prepares' the partition to be incorporated into the image.
145 In this case, prepare content for legacy bios boot partition.
146 """
147 if part.rootfs is None:
148 if not 'ROOTFS_DIR' in krootfs_dir:
149 msg = "Couldn't find --rootfs-dir, exiting"
150 msger.error(msg)
151 rootfs_dir = krootfs_dir['ROOTFS_DIR']
152 else:
153 if part.rootfs in krootfs_dir:
154 rootfs_dir = krootfs_dir[part.rootfs]
155 elif os.path.isdir(part.rootfs):
156 rootfs_dir = part.rootfs
157 else:
158 msg = "Couldn't find --rootfs-dir=%s connection"
159 msg += " or it is not a valid path, exiting"
160 msger.error(msg % part.rootfs)
161
162 part.set_rootfs(rootfs_dir)
163
164 # change partition label wich will reflect into the final rootfs image name
165 part.label = "%s_%s" % (part.label, cr.name)
166
167 defpath = os.environ['PATH']
168 os.environ['PATH'] = native_sysroot + "/usr/bin/" + ":/bin:/usr/bin:"
169
170 part.prepare_rootfs(cr_workdir, oe_builddir, rootfs_dir, native_sysroot)
171 part.prepare_for_uboot(cr.target_arch,cr_workdir, oe_builddir, rootfs_dir, native_sysroot)
172
173 os.environ['PATH'] += defpath + ":" + native_sysroot + "/usr/bin/"
diff --git a/scripts/lib/wic/test b/scripts/lib/wic/test
new file mode 100644
index 0000000000..9daeafb986
--- /dev/null
+++ b/scripts/lib/wic/test
@@ -0,0 +1 @@
test
diff --git a/scripts/lib/wic/utils/__init__.py b/scripts/lib/wic/utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/lib/wic/utils/__init__.py
diff --git a/scripts/lib/wic/utils/cmdln.py b/scripts/lib/wic/utils/cmdln.py
new file mode 100644
index 0000000000..b099473ee4
--- /dev/null
+++ b/scripts/lib/wic/utils/cmdln.py
@@ -0,0 +1,1586 @@
1#!/usr/bin/env python
2# Copyright (c) 2002-2007 ActiveState Software Inc.
3# License: MIT (see LICENSE.txt for license details)
4# Author: Trent Mick
5# Home: http://trentm.com/projects/cmdln/
6
7"""An improvement on Python's standard cmd.py module.
8
9As with cmd.py, this module provides "a simple framework for writing
10line-oriented command intepreters." This module provides a 'RawCmdln'
11class that fixes some design flaws in cmd.Cmd, making it more scalable
12and nicer to use for good 'cvs'- or 'svn'-style command line interfaces
13or simple shells. And it provides a 'Cmdln' class that add
14optparse-based option processing. Basically you use it like this:
15
16 import cmdln
17
18 class MySVN(cmdln.Cmdln):
19 name = "svn"
20
21 @cmdln.alias('stat', 'st')
22 @cmdln.option('-v', '--verbose', action='store_true'
23 help='print verbose information')
24 def do_status(self, subcmd, opts, *paths):
25 print "handle 'svn status' command"
26
27 #...
28
29 if __name__ == "__main__":
30 shell = MySVN()
31 retval = shell.main()
32 sys.exit(retval)
33
34See the README.txt or <http://trentm.com/projects/cmdln/> for more
35details.
36"""
37
38__version_info__ = (1, 1, 2)
39__version__ = '.'.join(map(str, __version_info__))
40
41import os
42import sys
43import re
44import cmd
45import optparse
46from pprint import pprint
47import sys
48
49
50
51
52#---- globals
53
54LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3)
55
56# An unspecified optional argument when None is a meaningful value.
57_NOT_SPECIFIED = ("Not", "Specified")
58
59# Pattern to match a TypeError message from a call that
60# failed because of incorrect number of arguments (see
61# Python/getargs.c).
62_INCORRECT_NUM_ARGS_RE = re.compile(
63 r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))")
64
65
66
67#---- exceptions
68
69class CmdlnError(Exception):
70 """A cmdln.py usage error."""
71 def __init__(self, msg):
72 self.msg = msg
73 def __str__(self):
74 return self.msg
75
76class CmdlnUserError(Exception):
77 """An error by a user of a cmdln-based tool/shell."""
78 pass
79
80
81
82#---- public methods and classes
83
84def alias(*aliases):
85 """Decorator to add aliases for Cmdln.do_* command handlers.
86
87 Example:
88 class MyShell(cmdln.Cmdln):
89 @cmdln.alias("!", "sh")
90 def do_shell(self, argv):
91 #...implement 'shell' command
92 """
93 def decorate(f):
94 if not hasattr(f, "aliases"):
95 f.aliases = []
96 f.aliases += aliases
97 return f
98 return decorate
99
100
101class RawCmdln(cmd.Cmd):
102 """An improved (on cmd.Cmd) framework for building multi-subcommand
103 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
104 "gdb").
105
106 A simple example:
107
108 import cmdln
109
110 class MySVN(cmdln.RawCmdln):
111 name = "svn"
112
113 @cmdln.aliases('stat', 'st')
114 def do_status(self, argv):
115 print "handle 'svn status' command"
116
117 if __name__ == "__main__":
118 shell = MySVN()
119 retval = shell.main()
120 sys.exit(retval)
121
122 See <http://trentm.com/projects/cmdln> for more information.
123 """
124 name = None # if unset, defaults basename(sys.argv[0])
125 prompt = None # if unset, defaults to self.name+"> "
126 version = None # if set, default top-level options include --version
127
128 # Default messages for some 'help' command error cases.
129 # They are interpolated with one arg: the command.
130 nohelp = "no help on '%s'"
131 unknowncmd = "unknown command: '%s'"
132
133 helpindent = '' # string with which to indent help output
134
135 def __init__(self, completekey='tab',
136 stdin=None, stdout=None, stderr=None):
137 """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None)
138
139 The optional argument 'completekey' is the readline name of a
140 completion key; it defaults to the Tab key. If completekey is
141 not None and the readline module is available, command completion
142 is done automatically.
143
144 The optional arguments 'stdin', 'stdout' and 'stderr' specify
145 alternate input, output and error output file objects; if not
146 specified, sys.* are used.
147
148 If 'stdout' but not 'stderr' is specified, stdout is used for
149 error output. This is to provide least surprise for users used
150 to only the 'stdin' and 'stdout' options with cmd.Cmd.
151 """
152 import sys
153 if self.name is None:
154 self.name = os.path.basename(sys.argv[0])
155 if self.prompt is None:
156 self.prompt = self.name+"> "
157 self._name_str = self._str(self.name)
158 self._prompt_str = self._str(self.prompt)
159 if stdin is not None:
160 self.stdin = stdin
161 else:
162 self.stdin = sys.stdin
163 if stdout is not None:
164 self.stdout = stdout
165 else:
166 self.stdout = sys.stdout
167 if stderr is not None:
168 self.stderr = stderr
169 elif stdout is not None:
170 self.stderr = stdout
171 else:
172 self.stderr = sys.stderr
173 self.cmdqueue = []
174 self.completekey = completekey
175 self.cmdlooping = False
176
177 def get_optparser(self):
178 """Hook for subclasses to set the option parser for the
179 top-level command/shell.
180
181 This option parser is used retrieved and used by `.main()' to
182 handle top-level options.
183
184 The default implements a single '-h|--help' option. Sub-classes
185 can return None to have no options at the top-level. Typically
186 an instance of CmdlnOptionParser should be returned.
187 """
188 version = (self.version is not None
189 and "%s %s" % (self._name_str, self.version)
190 or None)
191 return CmdlnOptionParser(self, version=version)
192
193 def postoptparse(self):
194 """Hook method executed just after `.main()' parses top-level
195 options.
196
197 When called `self.options' holds the results of the option parse.
198 """
199 pass
200
201 def main(self, argv=None, loop=LOOP_NEVER):
202 """A possible mainline handler for a script, like so:
203
204 import cmdln
205 class MyCmd(cmdln.Cmdln):
206 name = "mycmd"
207 ...
208
209 if __name__ == "__main__":
210 MyCmd().main()
211
212 By default this will use sys.argv to issue a single command to
213 'MyCmd', then exit. The 'loop' argument can be use to control
214 interactive shell behaviour.
215
216 Arguments:
217 "argv" (optional, default sys.argv) is the command to run.
218 It must be a sequence, where the first element is the
219 command name and subsequent elements the args for that
220 command.
221 "loop" (optional, default LOOP_NEVER) is a constant
222 indicating if a command loop should be started (i.e. an
223 interactive shell). Valid values (constants on this module):
224 LOOP_ALWAYS start loop and run "argv", if any
225 LOOP_NEVER run "argv" (or .emptyline()) and exit
226 LOOP_IF_EMPTY run "argv", if given, and exit;
227 otherwise, start loop
228 """
229 if argv is None:
230 import sys
231 argv = sys.argv
232 else:
233 argv = argv[:] # don't modify caller's list
234
235 self.optparser = self.get_optparser()
236 if self.optparser: # i.e. optparser=None means don't process for opts
237 try:
238 self.options, args = self.optparser.parse_args(argv[1:])
239 except CmdlnUserError, ex:
240 msg = "%s: %s\nTry '%s help' for info.\n"\
241 % (self.name, ex, self.name)
242 self.stderr.write(self._str(msg))
243 self.stderr.flush()
244 return 1
245 except StopOptionProcessing, ex:
246 return 0
247 else:
248 self.options, args = None, argv[1:]
249 self.postoptparse()
250
251 if loop == LOOP_ALWAYS:
252 if args:
253 self.cmdqueue.append(args)
254 return self.cmdloop()
255 elif loop == LOOP_NEVER:
256 if args:
257 return self.cmd(args)
258 else:
259 return self.emptyline()
260 elif loop == LOOP_IF_EMPTY:
261 if args:
262 return self.cmd(args)
263 else:
264 return self.cmdloop()
265
266 def cmd(self, argv):
267 """Run one command and exit.
268
269 "argv" is the arglist for the command to run. argv[0] is the
270 command to run. If argv is an empty list then the
271 'emptyline' handler is run.
272
273 Returns the return value from the command handler.
274 """
275 assert isinstance(argv, (list, tuple)), \
276 "'argv' is not a sequence: %r" % argv
277 retval = None
278 try:
279 argv = self.precmd(argv)
280 retval = self.onecmd(argv)
281 self.postcmd(argv)
282 except:
283 if not self.cmdexc(argv):
284 raise
285 retval = 1
286 return retval
287
288 def _str(self, s):
289 """Safely convert the given str/unicode to a string for printing."""
290 try:
291 return str(s)
292 except UnicodeError:
293 #XXX What is the proper encoding to use here? 'utf-8' seems
294 # to work better than "getdefaultencoding" (usually
295 # 'ascii'), on OS X at least.
296 #import sys
297 #return s.encode(sys.getdefaultencoding(), "replace")
298 return s.encode("utf-8", "replace")
299
300 def cmdloop(self, intro=None):
301 """Repeatedly issue a prompt, accept input, parse into an argv, and
302 dispatch (via .precmd(), .onecmd() and .postcmd()), passing them
303 the argv. In other words, start a shell.
304
305 "intro" (optional) is a introductory message to print when
306 starting the command loop. This overrides the class
307 "intro" attribute, if any.
308 """
309 self.cmdlooping = True
310 self.preloop()
311 if self.use_rawinput and self.completekey:
312 try:
313 import readline
314 self.old_completer = readline.get_completer()
315 readline.set_completer(self.complete)
316 readline.parse_and_bind(self.completekey+": complete")
317 except ImportError:
318 pass
319 try:
320 if intro is None:
321 intro = self.intro
322 if intro:
323 intro_str = self._str(intro)
324 self.stdout.write(intro_str+'\n')
325 self.stop = False
326 retval = None
327 while not self.stop:
328 if self.cmdqueue:
329 argv = self.cmdqueue.pop(0)
330 assert isinstance(argv, (list, tuple)), \
331 "item on 'cmdqueue' is not a sequence: %r" % argv
332 else:
333 if self.use_rawinput:
334 try:
335 line = raw_input(self._prompt_str)
336 except EOFError:
337 line = 'EOF'
338 else:
339 self.stdout.write(self._prompt_str)
340 self.stdout.flush()
341 line = self.stdin.readline()
342 if not len(line):
343 line = 'EOF'
344 else:
345 line = line[:-1] # chop '\n'
346 argv = line2argv(line)
347 try:
348 argv = self.precmd(argv)
349 retval = self.onecmd(argv)
350 self.postcmd(argv)
351 except:
352 if not self.cmdexc(argv):
353 raise
354 retval = 1
355 self.lastretval = retval
356 self.postloop()
357 finally:
358 if self.use_rawinput and self.completekey:
359 try:
360 import readline
361 readline.set_completer(self.old_completer)
362 except ImportError:
363 pass
364 self.cmdlooping = False
365 return retval
366
367 def precmd(self, argv):
368 """Hook method executed just before the command argv is
369 interpreted, but after the input prompt is generated and issued.
370
371 "argv" is the cmd to run.
372
373 Returns an argv to run (i.e. this method can modify the command
374 to run).
375 """
376 return argv
377
378 def postcmd(self, argv):
379 """Hook method executed just after a command dispatch is finished.
380
381 "argv" is the command that was run.
382 """
383 pass
384
385 def cmdexc(self, argv):
386 """Called if an exception is raised in any of precmd(), onecmd(),
387 or postcmd(). If True is returned, the exception is deemed to have
388 been dealt with. Otherwise, the exception is re-raised.
389
390 The default implementation handles CmdlnUserError's, which
391 typically correspond to user error in calling commands (as
392 opposed to programmer error in the design of the script using
393 cmdln.py).
394 """
395 import sys
396 type, exc, traceback = sys.exc_info()
397 if isinstance(exc, CmdlnUserError):
398 msg = "%s %s: %s\nTry '%s help %s' for info.\n"\
399 % (self.name, argv[0], exc, self.name, argv[0])
400 self.stderr.write(self._str(msg))
401 self.stderr.flush()
402 return True
403
404 def onecmd(self, argv):
405 if not argv:
406 return self.emptyline()
407 self.lastcmd = argv
408 cmdname = self._get_canonical_cmd_name(argv[0])
409 if cmdname:
410 handler = self._get_cmd_handler(cmdname)
411 if handler:
412 return self._dispatch_cmd(handler, argv)
413 return self.default(argv)
414
415 def _dispatch_cmd(self, handler, argv):
416 return handler(argv)
417
418 def default(self, argv):
419 """Hook called to handle a command for which there is no handler.
420
421 "argv" is the command and arguments to run.
422
423 The default implementation writes and error message to stderr
424 and returns an error exit status.
425
426 Returns a numeric command exit status.
427 """
428 errmsg = self._str(self.unknowncmd % (argv[0],))
429 if self.cmdlooping:
430 self.stderr.write(errmsg+"\n")
431 else:
432 self.stderr.write("%s: %s\nTry '%s help' for info.\n"
433 % (self._name_str, errmsg, self._name_str))
434 self.stderr.flush()
435 return 1
436
437 def parseline(self, line):
438 # This is used by Cmd.complete (readline completer function) to
439 # massage the current line buffer before completion processing.
440 # We override to drop special '!' handling.
441 line = line.strip()
442 if not line:
443 return None, None, line
444 elif line[0] == '?':
445 line = 'help ' + line[1:]
446 i, n = 0, len(line)
447 while i < n and line[i] in self.identchars: i = i+1
448 cmd, arg = line[:i], line[i:].strip()
449 return cmd, arg, line
450
451 def helpdefault(self, cmd, known):
452 """Hook called to handle help on a command for which there is no
453 help handler.
454
455 "cmd" is the command name on which help was requested.
456 "known" is a boolean indicating if this command is known
457 (i.e. if there is a handler for it).
458
459 Returns a return code.
460 """
461 if known:
462 msg = self._str(self.nohelp % (cmd,))
463 if self.cmdlooping:
464 self.stderr.write(msg + '\n')
465 else:
466 self.stderr.write("%s: %s\n" % (self.name, msg))
467 else:
468 msg = self.unknowncmd % (cmd,)
469 if self.cmdlooping:
470 self.stderr.write(msg + '\n')
471 else:
472 self.stderr.write("%s: %s\n"
473 "Try '%s help' for info.\n"
474 % (self.name, msg, self.name))
475 self.stderr.flush()
476 return 1
477
478 def do_help(self, argv):
479 """${cmd_name}: give detailed help on a specific sub-command
480
481 Usage:
482 ${name} help [COMMAND]
483 """
484 if len(argv) > 1: # asking for help on a particular command
485 doc = None
486 cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1]
487 if not cmdname:
488 return self.helpdefault(argv[1], False)
489 else:
490 helpfunc = getattr(self, "help_"+cmdname, None)
491 if helpfunc:
492 doc = helpfunc()
493 else:
494 handler = self._get_cmd_handler(cmdname)
495 if handler:
496 doc = handler.__doc__
497 if doc is None:
498 return self.helpdefault(argv[1], handler != None)
499 else: # bare "help" command
500 doc = self.__class__.__doc__ # try class docstring
501 if doc is None:
502 # Try to provide some reasonable useful default help.
503 if self.cmdlooping: prefix = ""
504 else: prefix = self.name+' '
505 doc = """Usage:
506 %sCOMMAND [ARGS...]
507 %shelp [COMMAND]
508
509 ${option_list}
510 ${command_list}
511 ${help_list}
512 """ % (prefix, prefix)
513 cmdname = None
514
515 if doc: # *do* have help content, massage and print that
516 doc = self._help_reindent(doc)
517 doc = self._help_preprocess(doc, cmdname)
518 doc = doc.rstrip() + '\n' # trim down trailing space
519 self.stdout.write(self._str(doc))
520 self.stdout.flush()
521 do_help.aliases = ["?"]
522
523 def _help_reindent(self, help, indent=None):
524 """Hook to re-indent help strings before writing to stdout.
525
526 "help" is the help content to re-indent
527 "indent" is a string with which to indent each line of the
528 help content after normalizing. If unspecified or None
529 then the default is use: the 'self.helpindent' class
530 attribute. By default this is the empty string, i.e.
531 no indentation.
532
533 By default, all common leading whitespace is removed and then
534 the lot is indented by 'self.helpindent'. When calculating the
535 common leading whitespace the first line is ignored -- hence
536 help content for Conan can be written as follows and have the
537 expected indentation:
538
539 def do_crush(self, ...):
540 '''${cmd_name}: crush your enemies, see them driven before you...
541
542 c.f. Conan the Barbarian'''
543 """
544 if indent is None:
545 indent = self.helpindent
546 lines = help.splitlines(0)
547 _dedentlines(lines, skip_first_line=True)
548 lines = [(indent+line).rstrip() for line in lines]
549 return '\n'.join(lines)
550
551 def _help_preprocess(self, help, cmdname):
552 """Hook to preprocess a help string before writing to stdout.
553
554 "help" is the help string to process.
555 "cmdname" is the canonical sub-command name for which help
556 is being given, or None if the help is not specific to a
557 command.
558
559 By default the following template variables are interpolated in
560 help content. (Note: these are similar to Python 2.4's
561 string.Template interpolation but not quite.)
562
563 ${name}
564 The tool's/shell's name, i.e. 'self.name'.
565 ${option_list}
566 A formatted table of options for this shell/tool.
567 ${command_list}
568 A formatted table of available sub-commands.
569 ${help_list}
570 A formatted table of additional help topics (i.e. 'help_*'
571 methods with no matching 'do_*' method).
572 ${cmd_name}
573 The name (and aliases) for this sub-command formatted as:
574 "NAME (ALIAS1, ALIAS2, ...)".
575 ${cmd_usage}
576 A formatted usage block inferred from the command function
577 signature.
578 ${cmd_option_list}
579 A formatted table of options for this sub-command. (This is
580 only available for commands using the optparse integration,
581 i.e. using @cmdln.option decorators or manually setting the
582 'optparser' attribute on the 'do_*' method.)
583
584 Returns the processed help.
585 """
586 preprocessors = {
587 "${name}": self._help_preprocess_name,
588 "${option_list}": self._help_preprocess_option_list,
589 "${command_list}": self._help_preprocess_command_list,
590 "${help_list}": self._help_preprocess_help_list,
591 "${cmd_name}": self._help_preprocess_cmd_name,
592 "${cmd_usage}": self._help_preprocess_cmd_usage,
593 "${cmd_option_list}": self._help_preprocess_cmd_option_list,
594 }
595
596 for marker, preprocessor in preprocessors.items():
597 if marker in help:
598 help = preprocessor(help, cmdname)
599 return help
600
601 def _help_preprocess_name(self, help, cmdname=None):
602 return help.replace("${name}", self.name)
603
604 def _help_preprocess_option_list(self, help, cmdname=None):
605 marker = "${option_list}"
606 indent, indent_width = _get_indent(marker, help)
607 suffix = _get_trailing_whitespace(marker, help)
608
609 if self.optparser:
610 # Setup formatting options and format.
611 # - Indentation of 4 is better than optparse default of 2.
612 # C.f. Damian Conway's discussion of this in Perl Best
613 # Practices.
614 self.optparser.formatter.indent_increment = 4
615 self.optparser.formatter.current_indent = indent_width
616 block = self.optparser.format_option_help() + '\n'
617 else:
618 block = ""
619
620 help = help.replace(indent+marker+suffix, block, 1)
621 return help
622
623
624 def _help_preprocess_command_list(self, help, cmdname=None):
625 marker = "${command_list}"
626 indent, indent_width = _get_indent(marker, help)
627 suffix = _get_trailing_whitespace(marker, help)
628
629 # Find any aliases for commands.
630 token2canonical = self._get_canonical_map()
631 aliases = {}
632 for token, cmdname in token2canonical.items():
633 if token == cmdname: continue
634 aliases.setdefault(cmdname, []).append(token)
635
636 # Get the list of (non-hidden) commands and their
637 # documentation, if any.
638 cmdnames = {} # use a dict to strip duplicates
639 for attr in self.get_names():
640 if attr.startswith("do_"):
641 cmdnames[attr[3:]] = True
642 cmdnames = cmdnames.keys()
643 cmdnames.sort()
644 linedata = []
645 for cmdname in cmdnames:
646 if aliases.get(cmdname):
647 a = aliases[cmdname]
648 a.sort()
649 cmdstr = "%s (%s)" % (cmdname, ", ".join(a))
650 else:
651 cmdstr = cmdname
652 doc = None
653 try:
654 helpfunc = getattr(self, 'help_'+cmdname)
655 except AttributeError:
656 handler = self._get_cmd_handler(cmdname)
657 if handler:
658 doc = handler.__doc__
659 else:
660 doc = helpfunc()
661
662 # Strip "${cmd_name}: " from the start of a command's doc. Best
663 # practice dictates that command help strings begin with this, but
664 # it isn't at all wanted for the command list.
665 to_strip = "${cmd_name}:"
666 if doc and doc.startswith(to_strip):
667 #log.debug("stripping %r from start of %s's help string",
668 # to_strip, cmdname)
669 doc = doc[len(to_strip):].lstrip()
670 linedata.append( (cmdstr, doc) )
671
672 if linedata:
673 subindent = indent + ' '*4
674 lines = _format_linedata(linedata, subindent, indent_width+4)
675 block = indent + "Commands:\n" \
676 + '\n'.join(lines) + "\n\n"
677 help = help.replace(indent+marker+suffix, block, 1)
678 return help
679
680 def _gen_names_and_attrs(self):
681 # Inheritance says we have to look in class and
682 # base classes; order is not important.
683 names = []
684 classes = [self.__class__]
685 while classes:
686 aclass = classes.pop(0)
687 if aclass.__bases__:
688 classes = classes + list(aclass.__bases__)
689 for name in dir(aclass):
690 yield (name, getattr(aclass, name))
691
692 def _help_preprocess_help_list(self, help, cmdname=None):
693 marker = "${help_list}"
694 indent, indent_width = _get_indent(marker, help)
695 suffix = _get_trailing_whitespace(marker, help)
696
697 # Determine the additional help topics, if any.
698 helpnames = {}
699 token2cmdname = self._get_canonical_map()
700 for attrname, attr in self._gen_names_and_attrs():
701 if not attrname.startswith("help_"): continue
702 helpname = attrname[5:]
703 if helpname not in token2cmdname:
704 helpnames[helpname] = attr
705
706 if helpnames:
707 linedata = [(n, a.__doc__ or "") for n, a in helpnames.items()]
708 linedata.sort()
709
710 subindent = indent + ' '*4
711 lines = _format_linedata(linedata, subindent, indent_width+4)
712 block = (indent
713 + "Additional help topics (run `%s help TOPIC'):\n" % self.name
714 + '\n'.join(lines)
715 + "\n\n")
716 else:
717 block = ''
718 help = help.replace(indent+marker+suffix, block, 1)
719 return help
720
721 def _help_preprocess_cmd_name(self, help, cmdname=None):
722 marker = "${cmd_name}"
723 handler = self._get_cmd_handler(cmdname)
724 if not handler:
725 raise CmdlnError("cannot preprocess '%s' into help string: "
726 "could not find command handler for %r"
727 % (marker, cmdname))
728 s = cmdname
729 if hasattr(handler, "aliases"):
730 s += " (%s)" % (", ".join(handler.aliases))
731 help = help.replace(marker, s)
732 return help
733
734 #TODO: this only makes sense as part of the Cmdln class.
735 # Add hooks to add help preprocessing template vars and put
736 # this one on that class.
737 def _help_preprocess_cmd_usage(self, help, cmdname=None):
738 marker = "${cmd_usage}"
739 handler = self._get_cmd_handler(cmdname)
740 if not handler:
741 raise CmdlnError("cannot preprocess '%s' into help string: "
742 "could not find command handler for %r"
743 % (marker, cmdname))
744 indent, indent_width = _get_indent(marker, help)
745 suffix = _get_trailing_whitespace(marker, help)
746
747 # Extract the introspection bits we need.
748 func = handler.im_func
749 if func.func_defaults:
750 func_defaults = list(func.func_defaults)
751 else:
752 func_defaults = []
753 co_argcount = func.func_code.co_argcount
754 co_varnames = func.func_code.co_varnames
755 co_flags = func.func_code.co_flags
756 CO_FLAGS_ARGS = 4
757 CO_FLAGS_KWARGS = 8
758
759 # Adjust argcount for possible *args and **kwargs arguments.
760 argcount = co_argcount
761 if co_flags & CO_FLAGS_ARGS: argcount += 1
762 if co_flags & CO_FLAGS_KWARGS: argcount += 1
763
764 # Determine the usage string.
765 usage = "%s %s" % (self.name, cmdname)
766 if argcount <= 2: # handler ::= do_FOO(self, argv)
767 usage += " [ARGS...]"
768 elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...)
769 argnames = list(co_varnames[3:argcount])
770 tail = ""
771 if co_flags & CO_FLAGS_KWARGS:
772 name = argnames.pop(-1)
773 import warnings
774 # There is no generally accepted mechanism for passing
775 # keyword arguments from the command line. Could
776 # *perhaps* consider: arg=value arg2=value2 ...
777 warnings.warn("argument '**%s' on '%s.%s' command "
778 "handler will never get values"
779 % (name, self.__class__.__name__,
780 func.func_name))
781 if co_flags & CO_FLAGS_ARGS:
782 name = argnames.pop(-1)
783 tail = "[%s...]" % name.upper()
784 while func_defaults:
785 func_defaults.pop(-1)
786 name = argnames.pop(-1)
787 tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail)
788 while argnames:
789 name = argnames.pop(-1)
790 tail = "%s %s" % (name.upper(), tail)
791 usage += ' ' + tail
792
793 block_lines = [
794 self.helpindent + "Usage:",
795 self.helpindent + ' '*4 + usage
796 ]
797 block = '\n'.join(block_lines) + '\n\n'
798
799 help = help.replace(indent+marker+suffix, block, 1)
800 return help
801
802 #TODO: this only makes sense as part of the Cmdln class.
803 # Add hooks to add help preprocessing template vars and put
804 # this one on that class.
805 def _help_preprocess_cmd_option_list(self, help, cmdname=None):
806 marker = "${cmd_option_list}"
807 handler = self._get_cmd_handler(cmdname)
808 if not handler:
809 raise CmdlnError("cannot preprocess '%s' into help string: "
810 "could not find command handler for %r"
811 % (marker, cmdname))
812 indent, indent_width = _get_indent(marker, help)
813 suffix = _get_trailing_whitespace(marker, help)
814 if hasattr(handler, "optparser"):
815 # Setup formatting options and format.
816 # - Indentation of 4 is better than optparse default of 2.
817 # C.f. Damian Conway's discussion of this in Perl Best
818 # Practices.
819 handler.optparser.formatter.indent_increment = 4
820 handler.optparser.formatter.current_indent = indent_width
821 block = handler.optparser.format_option_help() + '\n'
822 else:
823 block = ""
824
825 help = help.replace(indent+marker+suffix, block, 1)
826 return help
827
828 def _get_canonical_cmd_name(self, token):
829 map = self._get_canonical_map()
830 return map.get(token, None)
831
832 def _get_canonical_map(self):
833 """Return a mapping of available command names and aliases to
834 their canonical command name.
835 """
836 cacheattr = "_token2canonical"
837 if not hasattr(self, cacheattr):
838 # Get the list of commands and their aliases, if any.
839 token2canonical = {}
840 cmd2funcname = {} # use a dict to strip duplicates
841 for attr in self.get_names():
842 if attr.startswith("do_"): cmdname = attr[3:]
843 elif attr.startswith("_do_"): cmdname = attr[4:]
844 else:
845 continue
846 cmd2funcname[cmdname] = attr
847 token2canonical[cmdname] = cmdname
848 for cmdname, funcname in cmd2funcname.items(): # add aliases
849 func = getattr(self, funcname)
850 aliases = getattr(func, "aliases", [])
851 for alias in aliases:
852 if alias in cmd2funcname:
853 import warnings
854 warnings.warn("'%s' alias for '%s' command conflicts "
855 "with '%s' handler"
856 % (alias, cmdname, cmd2funcname[alias]))
857 continue
858 token2canonical[alias] = cmdname
859 setattr(self, cacheattr, token2canonical)
860 return getattr(self, cacheattr)
861
862 def _get_cmd_handler(self, cmdname):
863 handler = None
864 try:
865 handler = getattr(self, 'do_' + cmdname)
866 except AttributeError:
867 try:
868 # Private command handlers begin with "_do_".
869 handler = getattr(self, '_do_' + cmdname)
870 except AttributeError:
871 pass
872 return handler
873
874 def _do_EOF(self, argv):
875 # Default EOF handler
876 # Note: an actual EOF is redirected to this command.
877 #TODO: separate name for this. Currently it is available from
878 # command-line. Is that okay?
879 self.stdout.write('\n')
880 self.stdout.flush()
881 self.stop = True
882
883 def emptyline(self):
884 # Different from cmd.Cmd: don't repeat the last command for an
885 # emptyline.
886 if self.cmdlooping:
887 pass
888 else:
889 return self.do_help(["help"])
890
891
892#---- optparse.py extension to fix (IMO) some deficiencies
893#
894# See the class _OptionParserEx docstring for details.
895#
896
897class StopOptionProcessing(Exception):
898 """Indicate that option *and argument* processing should stop
899 cleanly. This is not an error condition. It is similar in spirit to
900 StopIteration. This is raised by _OptionParserEx's default "help"
901 and "version" option actions and can be raised by custom option
902 callbacks too.
903
904 Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx)
905 usage is:
906
907 parser = CmdlnOptionParser(mycmd)
908 parser.add_option("-f", "--force", dest="force")
909 ...
910 try:
911 opts, args = parser.parse_args()
912 except StopOptionProcessing:
913 # normal termination, "--help" was probably given
914 sys.exit(0)
915 """
916
917class _OptionParserEx(optparse.OptionParser):
918 """An optparse.OptionParser that uses exceptions instead of sys.exit.
919
920 This class is an extension of optparse.OptionParser that differs
921 as follows:
922 - Correct (IMO) the default OptionParser error handling to never
923 sys.exit(). Instead OptParseError exceptions are passed through.
924 - Add the StopOptionProcessing exception (a la StopIteration) to
925 indicate normal termination of option processing.
926 See StopOptionProcessing's docstring for details.
927
928 I'd also like to see the following in the core optparse.py, perhaps
929 as a RawOptionParser which would serve as a base class for the more
930 generally used OptionParser (that works as current):
931 - Remove the implicit addition of the -h|--help and --version
932 options. They can get in the way (e.g. if want '-?' and '-V' for
933 these as well) and it is not hard to do:
934 optparser.add_option("-h", "--help", action="help")
935 optparser.add_option("--version", action="version")
936 These are good practices, just not valid defaults if they can
937 get in the way.
938 """
939 def error(self, msg):
940 raise optparse.OptParseError(msg)
941
942 def exit(self, status=0, msg=None):
943 if status == 0:
944 raise StopOptionProcessing(msg)
945 else:
946 #TODO: don't lose status info here
947 raise optparse.OptParseError(msg)
948
949
950
951#---- optparse.py-based option processing support
952
953class CmdlnOptionParser(_OptionParserEx):
954 """An optparse.OptionParser class more appropriate for top-level
955 Cmdln options. For parsing of sub-command options, see
956 SubCmdOptionParser.
957
958 Changes:
959 - disable_interspersed_args() by default, because a Cmdln instance
960 has sub-commands which may themselves have options.
961 - Redirect print_help() to the Cmdln.do_help() which is better
962 equiped to handle the "help" action.
963 - error() will raise a CmdlnUserError: OptionParse.error() is meant
964 to be called for user errors. Raising a well-known error here can
965 make error handling clearer.
966 - Also see the changes in _OptionParserEx.
967 """
968 def __init__(self, cmdln, **kwargs):
969 self.cmdln = cmdln
970 kwargs["prog"] = self.cmdln.name
971 _OptionParserEx.__init__(self, **kwargs)
972 self.disable_interspersed_args()
973
974 def print_help(self, file=None):
975 self.cmdln.onecmd(["help"])
976
977 def error(self, msg):
978 raise CmdlnUserError(msg)
979
980
981class SubCmdOptionParser(_OptionParserEx):
982 def set_cmdln_info(self, cmdln, subcmd):
983 """Called by Cmdln to pass relevant info about itself needed
984 for print_help().
985 """
986 self.cmdln = cmdln
987 self.subcmd = subcmd
988
989 def print_help(self, file=None):
990 self.cmdln.onecmd(["help", self.subcmd])
991
992 def error(self, msg):
993 raise CmdlnUserError(msg)
994
995
996def option(*args, **kwargs):
997 """Decorator to add an option to the optparser argument of a Cmdln
998 subcommand.
999
1000 Example:
1001 class MyShell(cmdln.Cmdln):
1002 @cmdln.option("-f", "--force", help="force removal")
1003 def do_remove(self, subcmd, opts, *args):
1004 #...
1005 """
1006 #XXX Is there a possible optimization for many options to not have a
1007 # large stack depth here?
1008 def decorate(f):
1009 if not hasattr(f, "optparser"):
1010 f.optparser = SubCmdOptionParser()
1011 f.optparser.add_option(*args, **kwargs)
1012 return f
1013 return decorate
1014
1015
1016class Cmdln(RawCmdln):
1017 """An improved (on cmd.Cmd) framework for building multi-subcommand
1018 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
1019 "gdb").
1020
1021 A simple example:
1022
1023 import cmdln
1024
1025 class MySVN(cmdln.Cmdln):
1026 name = "svn"
1027
1028 @cmdln.aliases('stat', 'st')
1029 @cmdln.option('-v', '--verbose', action='store_true'
1030 help='print verbose information')
1031 def do_status(self, subcmd, opts, *paths):
1032 print "handle 'svn status' command"
1033
1034 #...
1035
1036 if __name__ == "__main__":
1037 shell = MySVN()
1038 retval = shell.main()
1039 sys.exit(retval)
1040
1041 'Cmdln' extends 'RawCmdln' by providing optparse option processing
1042 integration. See this class' _dispatch_cmd() docstring and
1043 <http://trentm.com/projects/cmdln> for more information.
1044 """
1045 def _dispatch_cmd(self, handler, argv):
1046 """Introspect sub-command handler signature to determine how to
1047 dispatch the command. The raw handler provided by the base
1048 'RawCmdln' class is still supported:
1049
1050 def do_foo(self, argv):
1051 # 'argv' is the vector of command line args, argv[0] is
1052 # the command name itself (i.e. "foo" or an alias)
1053 pass
1054
1055 In addition, if the handler has more than 2 arguments option
1056 processing is automatically done (using optparse):
1057
1058 @cmdln.option('-v', '--verbose', action='store_true')
1059 def do_bar(self, subcmd, opts, *args):
1060 # subcmd = <"bar" or an alias>
1061 # opts = <an optparse.Values instance>
1062 if opts.verbose:
1063 print "lots of debugging output..."
1064 # args = <tuple of arguments>
1065 for arg in args:
1066 bar(arg)
1067
1068 TODO: explain that "*args" can be other signatures as well.
1069
1070 The `cmdln.option` decorator corresponds to an `add_option()`
1071 method call on an `optparse.OptionParser` instance.
1072
1073 You can declare a specific number of arguments:
1074
1075 @cmdln.option('-v', '--verbose', action='store_true')
1076 def do_bar2(self, subcmd, opts, bar_one, bar_two):
1077 #...
1078
1079 and an appropriate error message will be raised/printed if the
1080 command is called with a different number of args.
1081 """
1082 co_argcount = handler.im_func.func_code.co_argcount
1083 if co_argcount == 2: # handler ::= do_foo(self, argv)
1084 return handler(argv)
1085 elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...)
1086 try:
1087 optparser = handler.optparser
1088 except AttributeError:
1089 optparser = handler.im_func.optparser = SubCmdOptionParser()
1090 assert isinstance(optparser, SubCmdOptionParser)
1091 optparser.set_cmdln_info(self, argv[0])
1092 try:
1093 opts, args = optparser.parse_args(argv[1:])
1094 except StopOptionProcessing:
1095 #TODO: this doesn't really fly for a replacement of
1096 # optparse.py behaviour, does it?
1097 return 0 # Normal command termination
1098
1099 try:
1100 return handler(argv[0], opts, *args)
1101 except TypeError, ex:
1102 # Some TypeError's are user errors:
1103 # do_foo() takes at least 4 arguments (3 given)
1104 # do_foo() takes at most 5 arguments (6 given)
1105 # do_foo() takes exactly 5 arguments (6 given)
1106 # Raise CmdlnUserError for these with a suitably
1107 # massaged error message.
1108 import sys
1109 tb = sys.exc_info()[2] # the traceback object
1110 if tb.tb_next is not None:
1111 # If the traceback is more than one level deep, then the
1112 # TypeError do *not* happen on the "handler(...)" call
1113 # above. In that we don't want to handle it specially
1114 # here: it would falsely mask deeper code errors.
1115 raise
1116 msg = ex.args[0]
1117 match = _INCORRECT_NUM_ARGS_RE.search(msg)
1118 if match:
1119 msg = list(match.groups())
1120 msg[1] = int(msg[1]) - 3
1121 if msg[1] == 1:
1122 msg[2] = msg[2].replace("arguments", "argument")
1123 msg[3] = int(msg[3]) - 3
1124 msg = ''.join(map(str, msg))
1125 raise CmdlnUserError(msg)
1126 else:
1127 raise
1128 else:
1129 raise CmdlnError("incorrect argcount for %s(): takes %d, must "
1130 "take 2 for 'argv' signature or 3+ for 'opts' "
1131 "signature" % (handler.__name__, co_argcount))
1132
1133
1134
1135#---- internal support functions
1136
1137def _format_linedata(linedata, indent, indent_width):
1138 """Format specific linedata into a pleasant layout.
1139
1140 "linedata" is a list of 2-tuples of the form:
1141 (<item-display-string>, <item-docstring>)
1142 "indent" is a string to use for one level of indentation
1143 "indent_width" is a number of columns by which the
1144 formatted data will be indented when printed.
1145
1146 The <item-display-string> column is held to 15 columns.
1147 """
1148 lines = []
1149 WIDTH = 78 - indent_width
1150 SPACING = 2
1151 NAME_WIDTH_LOWER_BOUND = 13
1152 NAME_WIDTH_UPPER_BOUND = 16
1153 NAME_WIDTH = max([len(s) for s,d in linedata])
1154 if NAME_WIDTH < NAME_WIDTH_LOWER_BOUND:
1155 NAME_WIDTH = NAME_WIDTH_LOWER_BOUND
1156 else:
1157 NAME_WIDTH = NAME_WIDTH_UPPER_BOUND
1158
1159 DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING
1160 for namestr, doc in linedata:
1161 line = indent + namestr
1162 if len(namestr) <= NAME_WIDTH:
1163 line += ' ' * (NAME_WIDTH + SPACING - len(namestr))
1164 else:
1165 lines.append(line)
1166 line = indent + ' ' * (NAME_WIDTH + SPACING)
1167 line += _summarize_doc(doc, DOC_WIDTH)
1168 lines.append(line.rstrip())
1169 return lines
1170
1171def _summarize_doc(doc, length=60):
1172 r"""Parse out a short one line summary from the given doclines.
1173
1174 "doc" is the doc string to summarize.
1175 "length" is the max length for the summary
1176
1177 >>> _summarize_doc("this function does this")
1178 'this function does this'
1179 >>> _summarize_doc("this function does this", 10)
1180 'this fu...'
1181 >>> _summarize_doc("this function does this\nand that")
1182 'this function does this and that'
1183 >>> _summarize_doc("this function does this\n\nand that")
1184 'this function does this'
1185 """
1186 import re
1187 if doc is None:
1188 return ""
1189 assert length > 3, "length <= 3 is absurdly short for a doc summary"
1190 doclines = doc.strip().splitlines(0)
1191 if not doclines:
1192 return ""
1193
1194 summlines = []
1195 for i, line in enumerate(doclines):
1196 stripped = line.strip()
1197 if not stripped:
1198 break
1199 summlines.append(stripped)
1200 if len(''.join(summlines)) >= length:
1201 break
1202
1203 summary = ' '.join(summlines)
1204 if len(summary) > length:
1205 summary = summary[:length-3] + "..."
1206 return summary
1207
1208
1209def line2argv(line):
1210 r"""Parse the given line into an argument vector.
1211
1212 "line" is the line of input to parse.
1213
1214 This may get niggly when dealing with quoting and escaping. The
1215 current state of this parsing may not be completely thorough/correct
1216 in this respect.
1217
1218 >>> from cmdln import line2argv
1219 >>> line2argv("foo")
1220 ['foo']
1221 >>> line2argv("foo bar")
1222 ['foo', 'bar']
1223 >>> line2argv("foo bar ")
1224 ['foo', 'bar']
1225 >>> line2argv(" foo bar")
1226 ['foo', 'bar']
1227
1228 Quote handling:
1229
1230 >>> line2argv("'foo bar'")
1231 ['foo bar']
1232 >>> line2argv('"foo bar"')
1233 ['foo bar']
1234 >>> line2argv(r'"foo\"bar"')
1235 ['foo"bar']
1236 >>> line2argv("'foo bar' spam")
1237 ['foo bar', 'spam']
1238 >>> line2argv("'foo 'bar spam")
1239 ['foo bar', 'spam']
1240
1241 >>> line2argv('some\tsimple\ttests')
1242 ['some', 'simple', 'tests']
1243 >>> line2argv('a "more complex" test')
1244 ['a', 'more complex', 'test']
1245 >>> line2argv('a more="complex test of " quotes')
1246 ['a', 'more=complex test of ', 'quotes']
1247 >>> line2argv('a more" complex test of " quotes')
1248 ['a', 'more complex test of ', 'quotes']
1249 >>> line2argv('an "embedded \\"quote\\""')
1250 ['an', 'embedded "quote"']
1251
1252 # Komodo bug 48027
1253 >>> line2argv('foo bar C:\\')
1254 ['foo', 'bar', 'C:\\']
1255
1256 # Komodo change 127581
1257 >>> line2argv(r'"\test\slash" "foo bar" "foo\"bar"')
1258 ['\\test\\slash', 'foo bar', 'foo"bar']
1259
1260 # Komodo change 127629
1261 >>> if sys.platform == "win32":
1262 ... line2argv(r'\foo\bar') == ['\\foo\\bar']
1263 ... line2argv(r'\\foo\\bar') == ['\\\\foo\\\\bar']
1264 ... line2argv('"foo') == ['foo']
1265 ... else:
1266 ... line2argv(r'\foo\bar') == ['foobar']
1267 ... line2argv(r'\\foo\\bar') == ['\\foo\\bar']
1268 ... try:
1269 ... line2argv('"foo')
1270 ... except ValueError, ex:
1271 ... "not terminated" in str(ex)
1272 True
1273 True
1274 True
1275 """
1276 import string
1277 line = line.strip()
1278 argv = []
1279 state = "default"
1280 arg = None # the current argument being parsed
1281 i = -1
1282 while 1:
1283 i += 1
1284 if i >= len(line): break
1285 ch = line[i]
1286
1287 if ch == "\\" and i+1 < len(line):
1288 # escaped char always added to arg, regardless of state
1289 if arg is None: arg = ""
1290 if (sys.platform == "win32"
1291 or state in ("double-quoted", "single-quoted")
1292 ) and line[i+1] not in tuple('"\''):
1293 arg += ch
1294 i += 1
1295 arg += line[i]
1296 continue
1297
1298 if state == "single-quoted":
1299 if ch == "'":
1300 state = "default"
1301 else:
1302 arg += ch
1303 elif state == "double-quoted":
1304 if ch == '"':
1305 state = "default"
1306 else:
1307 arg += ch
1308 elif state == "default":
1309 if ch == '"':
1310 if arg is None: arg = ""
1311 state = "double-quoted"
1312 elif ch == "'":
1313 if arg is None: arg = ""
1314 state = "single-quoted"
1315 elif ch in string.whitespace:
1316 if arg is not None:
1317 argv.append(arg)
1318 arg = None
1319 else:
1320 if arg is None: arg = ""
1321 arg += ch
1322 if arg is not None:
1323 argv.append(arg)
1324 if not sys.platform == "win32" and state != "default":
1325 raise ValueError("command line is not terminated: unfinished %s "
1326 "segment" % state)
1327 return argv
1328
1329
1330def argv2line(argv):
1331 r"""Put together the given argument vector into a command line.
1332
1333 "argv" is the argument vector to process.
1334
1335 >>> from cmdln import argv2line
1336 >>> argv2line(['foo'])
1337 'foo'
1338 >>> argv2line(['foo', 'bar'])
1339 'foo bar'
1340 >>> argv2line(['foo', 'bar baz'])
1341 'foo "bar baz"'
1342 >>> argv2line(['foo"bar'])
1343 'foo"bar'
1344 >>> print argv2line(['foo" bar'])
1345 'foo" bar'
1346 >>> print argv2line(["foo' bar"])
1347 "foo' bar"
1348 >>> argv2line(["foo'bar"])
1349 "foo'bar"
1350 """
1351 escapedArgs = []
1352 for arg in argv:
1353 if ' ' in arg and '"' not in arg:
1354 arg = '"'+arg+'"'
1355 elif ' ' in arg and "'" not in arg:
1356 arg = "'"+arg+"'"
1357 elif ' ' in arg:
1358 arg = arg.replace('"', r'\"')
1359 arg = '"'+arg+'"'
1360 escapedArgs.append(arg)
1361 return ' '.join(escapedArgs)
1362
1363
1364# Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook
1365def _dedentlines(lines, tabsize=8, skip_first_line=False):
1366 """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
1367
1368 "lines" is a list of lines to dedent.
1369 "tabsize" is the tab width to use for indent width calculations.
1370 "skip_first_line" is a boolean indicating if the first line should
1371 be skipped for calculating the indent width and for dedenting.
1372 This is sometimes useful for docstrings and similar.
1373
1374 Same as dedent() except operates on a sequence of lines. Note: the
1375 lines list is modified **in-place**.
1376 """
1377 DEBUG = False
1378 if DEBUG:
1379 print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
1380 % (tabsize, skip_first_line)
1381 indents = []
1382 margin = None
1383 for i, line in enumerate(lines):
1384 if i == 0 and skip_first_line: continue
1385 indent = 0
1386 for ch in line:
1387 if ch == ' ':
1388 indent += 1
1389 elif ch == '\t':
1390 indent += tabsize - (indent % tabsize)
1391 elif ch in '\r\n':
1392 continue # skip all-whitespace lines
1393 else:
1394 break
1395 else:
1396 continue # skip all-whitespace lines
1397 if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
1398 if margin is None:
1399 margin = indent
1400 else:
1401 margin = min(margin, indent)
1402 if DEBUG: print "dedent: margin=%r" % margin
1403
1404 if margin is not None and margin > 0:
1405 for i, line in enumerate(lines):
1406 if i == 0 and skip_first_line: continue
1407 removed = 0
1408 for j, ch in enumerate(line):
1409 if ch == ' ':
1410 removed += 1
1411 elif ch == '\t':
1412 removed += tabsize - (removed % tabsize)
1413 elif ch in '\r\n':
1414 if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
1415 lines[i] = lines[i][j:]
1416 break
1417 else:
1418 raise ValueError("unexpected non-whitespace char %r in "
1419 "line %r while removing %d-space margin"
1420 % (ch, line, margin))
1421 if DEBUG:
1422 print "dedent: %r: %r -> removed %d/%d"\
1423 % (line, ch, removed, margin)
1424 if removed == margin:
1425 lines[i] = lines[i][j+1:]
1426 break
1427 elif removed > margin:
1428 lines[i] = ' '*(removed-margin) + lines[i][j+1:]
1429 break
1430 return lines
1431
1432def _dedent(text, tabsize=8, skip_first_line=False):
1433 """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
1434
1435 "text" is the text to dedent.
1436 "tabsize" is the tab width to use for indent width calculations.
1437 "skip_first_line" is a boolean indicating if the first line should
1438 be skipped for calculating the indent width and for dedenting.
1439 This is sometimes useful for docstrings and similar.
1440
1441 textwrap.dedent(s), but don't expand tabs to spaces
1442 """
1443 lines = text.splitlines(1)
1444 _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
1445 return ''.join(lines)
1446
1447
1448def _get_indent(marker, s, tab_width=8):
1449 """_get_indent(marker, s, tab_width=8) ->
1450 (<indentation-of-'marker'>, <indentation-width>)"""
1451 # Figure out how much the marker is indented.
1452 INDENT_CHARS = tuple(' \t')
1453 start = s.index(marker)
1454 i = start
1455 while i > 0:
1456 if s[i-1] not in INDENT_CHARS:
1457 break
1458 i -= 1
1459 indent = s[i:start]
1460 indent_width = 0
1461 for ch in indent:
1462 if ch == ' ':
1463 indent_width += 1
1464 elif ch == '\t':
1465 indent_width += tab_width - (indent_width % tab_width)
1466 return indent, indent_width
1467
1468def _get_trailing_whitespace(marker, s):
1469 """Return the whitespace content trailing the given 'marker' in string 's',
1470 up to and including a newline.
1471 """
1472 suffix = ''
1473 start = s.index(marker) + len(marker)
1474 i = start
1475 while i < len(s):
1476 if s[i] in ' \t':
1477 suffix += s[i]
1478 elif s[i] in '\r\n':
1479 suffix += s[i]
1480 if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n':
1481 suffix += s[i+1]
1482 break
1483 else:
1484 break
1485 i += 1
1486 return suffix
1487
1488
1489
1490#---- bash completion support
1491# Note: This is still experimental. I expect to change this
1492# significantly.
1493#
1494# To get Bash completion for a cmdln.Cmdln class, run the following
1495# bash command:
1496# $ complete -C 'python -m cmdln /path/to/script.py CmdlnClass' cmdname
1497# For example:
1498# $ complete -C 'python -m cmdln ~/bin/svn.py SVN' svn
1499#
1500#TODO: Simplify the above so don't have to given path to script (try to
1501# find it on PATH, if possible). Could also make class name
1502# optional if there is only one in the module (common case).
1503
1504if __name__ == "__main__" and len(sys.argv) == 6:
1505 def _log(s):
1506 return # no-op, comment out for debugging
1507 from os.path import expanduser
1508 fout = open(expanduser("~/tmp/bashcpln.log"), 'a')
1509 fout.write(str(s) + '\n')
1510 fout.close()
1511
1512 # Recipe: module_from_path (1.0.1+)
1513 def _module_from_path(path):
1514 import imp, os, sys
1515 path = os.path.expanduser(path)
1516 dir = os.path.dirname(path) or os.curdir
1517 name = os.path.splitext(os.path.basename(path))[0]
1518 sys.path.insert(0, dir)
1519 try:
1520 iinfo = imp.find_module(name, [dir])
1521 return imp.load_module(name, *iinfo)
1522 finally:
1523 sys.path.remove(dir)
1524
1525 def _get_bash_cplns(script_path, class_name, cmd_name,
1526 token, preceding_token):
1527 _log('--')
1528 _log('get_cplns(%r, %r, %r, %r, %r)'
1529 % (script_path, class_name, cmd_name, token, preceding_token))
1530 comp_line = os.environ["COMP_LINE"]
1531 comp_point = int(os.environ["COMP_POINT"])
1532 _log("COMP_LINE: %r" % comp_line)
1533 _log("COMP_POINT: %r" % comp_point)
1534
1535 try:
1536 script = _module_from_path(script_path)
1537 except ImportError, ex:
1538 _log("error importing `%s': %s" % (script_path, ex))
1539 return []
1540 shell = getattr(script, class_name)()
1541 cmd_map = shell._get_canonical_map()
1542 del cmd_map["EOF"]
1543
1544 # Determine if completing the sub-command name.
1545 parts = comp_line[:comp_point].split(None, 1)
1546 _log(parts)
1547 if len(parts) == 1 or not (' ' in parts[1] or '\t' in parts[1]):
1548 #TODO: if parts[1].startswith('-'): handle top-level opts
1549 _log("complete sub-command names")
1550 matches = {}
1551 for name, canon_name in cmd_map.items():
1552 if name.startswith(token):
1553 matches[name] = canon_name
1554 if not matches:
1555 return []
1556 elif len(matches) == 1:
1557 return matches.keys()
1558 elif len(set(matches.values())) == 1:
1559 return [matches.values()[0]]
1560 else:
1561 return matches.keys()
1562
1563 # Otherwise, complete options for the given sub-command.
1564 #TODO: refine this so it does the right thing with option args
1565 if token.startswith('-'):
1566 cmd_name = comp_line.split(None, 2)[1]
1567 try:
1568 cmd_canon_name = cmd_map[cmd_name]
1569 except KeyError:
1570 return []
1571 handler = shell._get_cmd_handler(cmd_canon_name)
1572 optparser = getattr(handler, "optparser", None)
1573 if optparser is None:
1574 optparser = SubCmdOptionParser()
1575 opt_strs = []
1576 for option in optparser.option_list:
1577 for opt_str in option._short_opts + option._long_opts:
1578 if opt_str.startswith(token):
1579 opt_strs.append(opt_str)
1580 return opt_strs
1581
1582 return []
1583
1584 for cpln in _get_bash_cplns(*sys.argv[1:]):
1585 print cpln
1586
diff --git a/scripts/lib/wic/utils/errors.py b/scripts/lib/wic/utils/errors.py
new file mode 100644
index 0000000000..9410311875
--- /dev/null
+++ b/scripts/lib/wic/utils/errors.py
@@ -0,0 +1,47 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2007 Red Hat, Inc.
4# Copyright (c) 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19class CreatorError(Exception):
20 """An exception base class for all imgcreate errors."""
21 keyword = '<creator>'
22
23 def __init__(self, msg):
24 self.msg = msg
25
26 def __str__(self):
27 if isinstance(self.msg, unicode):
28 self.msg = self.msg.encode('utf-8', 'ignore')
29 else:
30 self.msg = str(self.msg)
31 return self.keyword + self.msg
32
33class Usage(CreatorError):
34 keyword = '<usage>'
35
36 def __str__(self):
37 if isinstance(self.msg, unicode):
38 self.msg = self.msg.encode('utf-8', 'ignore')
39 else:
40 self.msg = str(self.msg)
41 return self.keyword + self.msg + ', please use "--help" for more info'
42
43class KsError(CreatorError):
44 keyword = '<kickstart>'
45
46class ImageError(CreatorError):
47 keyword = '<mount>'
diff --git a/scripts/lib/wic/utils/fs_related.py b/scripts/lib/wic/utils/fs_related.py
new file mode 100644
index 0000000000..ea9f85c60f
--- /dev/null
+++ b/scripts/lib/wic/utils/fs_related.py
@@ -0,0 +1,111 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2007, Red Hat, Inc.
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19from __future__ import with_statement
20import os
21import sys
22import errno
23import stat
24import random
25import string
26import time
27import uuid
28
29from wic import msger
30from wic.utils import runner
31from wic.utils.errors import *
32from wic.utils.oe.misc import *
33
34def find_binary_path(binary):
35 if os.environ.has_key("PATH"):
36 paths = os.environ["PATH"].split(":")
37 else:
38 paths = []
39 if os.environ.has_key("HOME"):
40 paths += [os.environ["HOME"] + "/bin"]
41 paths += ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
42
43 for path in paths:
44 bin_path = "%s/%s" % (path, binary)
45 if os.path.exists(bin_path):
46 return bin_path
47
48 print "External command '%s' not found, exiting." % binary
49 print " (Please install '%s' on your host system)" % binary
50 sys.exit(1)
51
52def makedirs(dirname):
53 """A version of os.makedirs() that doesn't throw an
54 exception if the leaf directory already exists.
55 """
56 try:
57 os.makedirs(dirname)
58 except OSError, err:
59 if err.errno != errno.EEXIST:
60 raise
61
62class Disk:
63 """
64 Generic base object for a disk.
65 """
66 def __init__(self, size, device = None):
67 self._device = device
68 self._size = size
69
70 def create(self):
71 pass
72
73 def cleanup(self):
74 pass
75
76 def get_device(self):
77 return self._device
78 def set_device(self, path):
79 self._device = path
80 device = property(get_device, set_device)
81
82 def get_size(self):
83 return self._size
84 size = property(get_size)
85
86
87class DiskImage(Disk):
88 """
89 A Disk backed by a file.
90 """
91 def __init__(self, image_file, size):
92 Disk.__init__(self, size)
93 self.image_file = image_file
94
95 def exists(self):
96 return os.path.exists(self.image_file)
97
98 def create(self):
99 if self.device is not None:
100 return
101
102 blocks = self.size / 1024
103 if self.size - blocks * 1024:
104 blocks += 1
105
106 # create disk image
107 dd_cmd = "dd if=/dev/zero of=%s bs=1024 seek=%d count=1" % \
108 (self.image_file, blocks)
109 exec_cmd(dd_cmd)
110
111 self.device = self.image_file
diff --git a/scripts/lib/wic/utils/misc.py b/scripts/lib/wic/utils/misc.py
new file mode 100644
index 0000000000..6e56316608
--- /dev/null
+++ b/scripts/lib/wic/utils/misc.py
@@ -0,0 +1,59 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2010, 2011 Intel Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import sys
20import time
21
22def build_name(kscfg, release=None, prefix = None, suffix = None):
23 """Construct and return an image name string.
24
25 This is a utility function to help create sensible name and fslabel
26 strings. The name is constructed using the sans-prefix-and-extension
27 kickstart filename and the supplied prefix and suffix.
28
29 kscfg -- a path to a kickstart file
30 release -- a replacement to suffix for image release
31 prefix -- a prefix to prepend to the name; defaults to None, which causes
32 no prefix to be used
33 suffix -- a suffix to append to the name; defaults to None, which causes
34 a YYYYMMDDHHMM suffix to be used
35
36 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
37
38 """
39 name = os.path.basename(kscfg)
40 idx = name.rfind('.')
41 if idx >= 0:
42 name = name[:idx]
43
44 if release is not None:
45 suffix = ""
46 if prefix is None:
47 prefix = ""
48 if suffix is None:
49 suffix = time.strftime("%Y%m%d%H%M")
50
51 if name.startswith(prefix):
52 name = name[len(prefix):]
53
54 prefix = "%s-" % prefix if prefix else ""
55 suffix = "-%s" % suffix if suffix else ""
56
57 ret = prefix + name + suffix
58
59 return ret
diff --git a/scripts/lib/wic/utils/oe/__init__.py b/scripts/lib/wic/utils/oe/__init__.py
new file mode 100644
index 0000000000..0a81575a74
--- /dev/null
+++ b/scripts/lib/wic/utils/oe/__init__.py
@@ -0,0 +1,22 @@
1#
2# OpenEmbedded wic utils library
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# AUTHORS
21# Tom Zanussi <tom.zanussi (at] linux.intel.com>
22#
diff --git a/scripts/lib/wic/utils/oe/misc.py b/scripts/lib/wic/utils/oe/misc.py
new file mode 100644
index 0000000000..b0b5baab73
--- /dev/null
+++ b/scripts/lib/wic/utils/oe/misc.py
@@ -0,0 +1,205 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This module provides a place to collect various wic-related utils
22# for the OpenEmbedded Image Tools.
23#
24# AUTHORS
25# Tom Zanussi <tom.zanussi (at] linux.intel.com>
26#
27
28from wic import msger
29from wic.utils import runner
30
31def __exec_cmd(cmd_and_args, as_shell = False, catch = 3):
32 """
33 Execute command, catching stderr, stdout
34
35 Need to execute as_shell if the command uses wildcards
36 """
37 msger.debug("__exec_cmd: %s" % cmd_and_args)
38 args = cmd_and_args.split()
39 msger.debug(args)
40
41 if (as_shell):
42 rc, out = runner.runtool(cmd_and_args, catch)
43 else:
44 rc, out = runner.runtool(args, catch)
45 out = out.strip()
46 msger.debug("__exec_cmd: output for %s (rc = %d): %s" % \
47 (cmd_and_args, rc, out))
48
49 return (rc, out)
50
51
52def exec_cmd(cmd_and_args, as_shell = False, catch = 3):
53 """
54 Execute command, catching stderr, stdout
55
56 Exits if rc non-zero
57 """
58 rc, out = __exec_cmd(cmd_and_args, as_shell, catch)
59
60 if rc != 0:
61 msger.error("exec_cmd: %s returned '%s' instead of 0" % (cmd_and_args, rc))
62
63 return out
64
65
66def exec_cmd_quiet(cmd_and_args, as_shell = False):
67 """
68 Execute command, catching nothing in the output
69
70 Exits if rc non-zero
71 """
72 return exec_cmd(cmd_and_args, as_shell, 0)
73
74
75def exec_native_cmd(cmd_and_args, native_sysroot, catch = 3):
76 """
77 Execute native command, catching stderr, stdout
78
79 Need to execute as_shell if the command uses wildcards
80
81 Always need to execute native commands as_shell
82 """
83 native_paths = \
84 "export PATH=%s/sbin:%s/usr/sbin:%s/usr/bin:$PATH" % \
85 (native_sysroot, native_sysroot, native_sysroot)
86 native_cmd_and_args = "%s;%s" % (native_paths, cmd_and_args)
87 msger.debug("exec_native_cmd: %s" % cmd_and_args)
88
89 args = cmd_and_args.split()
90 msger.debug(args)
91
92 rc, out = __exec_cmd(native_cmd_and_args, True, catch)
93
94 if rc == 127: # shell command-not-found
95 msger.error("A native (host) program required to build the image "
96 "was not found (see details above). Please make sure "
97 "it's installed and try again.")
98
99 return (rc, out)
100
101
102def exec_native_cmd_quiet(cmd_and_args, native_sysroot):
103 """
104 Execute native command, catching nothing in the output
105
106 Need to execute as_shell if the command uses wildcards
107
108 Always need to execute native commands as_shell
109 """
110 return exec_native_cmd(cmd_and_args, native_sysroot, 0)
111
112
113# kickstart doesn't support variable substution in commands, so this
114# is our current simplistic scheme for supporting that
115
116wks_vars = dict()
117
118def get_wks_var(key):
119 return wks_vars[key]
120
121def add_wks_var(key, val):
122 wks_vars[key] = val
123
124BOOTDD_EXTRA_SPACE = 16384
125IMAGE_EXTRA_SPACE = 10240
126IMAGE_OVERHEAD_FACTOR = 1.3
127
128__bitbake_env_lines = ""
129
130def set_bitbake_env_lines(bitbake_env_lines):
131 global __bitbake_env_lines
132 __bitbake_env_lines = bitbake_env_lines
133
134def get_bitbake_env_lines():
135 return __bitbake_env_lines
136
137def find_bitbake_env_lines(image_name):
138 """
139 If image_name is empty, plugins might still be able to use the
140 environment, so set it regardless.
141 """
142 if image_name:
143 bitbake_env_cmd = "bitbake -e %s" % image_name
144 else:
145 bitbake_env_cmd = "bitbake -e"
146 rc, bitbake_env_lines = __exec_cmd(bitbake_env_cmd)
147 if rc != 0:
148 print "Couldn't get '%s' output." % bitbake_env_cmd
149 return None
150
151 return bitbake_env_lines
152
153def find_artifact(bitbake_env_lines, variable):
154 """
155 Gather the build artifact for the current image (the image_name
156 e.g. core-image-minimal) for the current MACHINE set in local.conf
157 """
158 retval = ""
159
160 for line in bitbake_env_lines.split('\n'):
161 if (get_line_val(line, variable)):
162 retval = get_line_val(line, variable)
163 break
164
165 return retval
166
167def get_line_val(line, key):
168 """
169 Extract the value from the VAR="val" string
170 """
171 if line.startswith(key + "="):
172 stripped_line = line.split('=')[1]
173 stripped_line = stripped_line.replace('\"', '')
174 return stripped_line
175 return None
176
177def get_bitbake_var(key):
178 for line in __bitbake_env_lines.split('\n'):
179 if (get_line_val(line, key)):
180 val = get_line_val(line, key)
181 return val
182 return None
183
184def parse_sourceparams(sourceparams):
185 """
186 Split sourceparams string of the form key1=val1[,key2=val2,...]
187 into a dict. Also accepts valueless keys i.e. without =.
188
189 Returns dict of param key/val pairs (note that val may be None).
190 """
191 params_dict = {}
192
193 params = sourceparams.split(',')
194 if params:
195 for p in params:
196 if not p:
197 continue
198 if not '=' in p:
199 key = p
200 val = None
201 else:
202 key, val = p.split('=')
203 params_dict[key] = val
204
205 return params_dict
diff --git a/scripts/lib/wic/utils/oe/package_manager.py b/scripts/lib/wic/utils/oe/package_manager.py
new file mode 100644
index 0000000000..92ce98e2ce
--- /dev/null
+++ b/scripts/lib/wic/utils/oe/package_manager.py
@@ -0,0 +1,810 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2014, Enea AB.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the opkg package manager wrapper as a combination of
22# meta/lib/oe/package_manager.py and bitbake/lib/bb/utils.py files and
23# adaptation of those files to 'wic'.
24#
25# AUTHORS
26# Adrian Calianu <adrian.calianu (at] enea.com>
27#
28# This file incorporates work covered by the following copyright and
29# permission notice:
30#
31# meta/COPYING.GPLv2 (GPLv2)
32# meta/COPYING.MIT (MIT)
33#
34# Copyright (C) 2004 Michael Lauer
35#
36# Permission to use, copy, modify, and/or distribute this software
37# for any purpose with or without fee is hereby granted, provided
38# that the above copyright notice and this permission notice appear
39# in all copies.
40#
41# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
42# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
43# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
44# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
45# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
46# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
47# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
48# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
49
50
51from abc import ABCMeta, abstractmethod
52import os
53import glob
54import subprocess
55import shutil
56import multiprocessing
57import re
58import errno
59import fcntl
60
61from mic.utils.oe.misc import *
62from mic import msger
63
64def mkdirhier(directory):
65 """Create a directory like 'mkdir -p', but does not complain if
66 directory already exists like os.makedirs
67 """
68
69 try:
70 os.makedirs(directory)
71 except OSError as e:
72 if e.errno != errno.EEXIST:
73 raise e
74
75def remove(path, recurse=False):
76 """Equivalent to rm -f or rm -rf"""
77 if not path:
78 return
79 if recurse:
80 # shutil.rmtree(name) would be ideal but its too slow
81 subprocess.call(['rm', '-rf'] + glob.glob(path))
82 return
83 for name in glob.glob(path):
84 try:
85 os.unlink(name)
86 except OSError as exc:
87 if exc.errno != errno.ENOENT:
88 raise
89
90def lockfile(name, shared=False, retry=True):
91 """
92 Use the file fn as a lock file, return when the lock has been acquired.
93 Returns a variable to pass to unlockfile().
94 """
95 dirname = os.path.dirname(name)
96 mkdirhier(dirname)
97
98 if not os.access(dirname, os.W_OK):
99 logger.error("Unable to acquire lock '%s', directory is not writable",
100 name)
101 sys.exit(1)
102
103 op = fcntl.LOCK_EX
104 if shared:
105 op = fcntl.LOCK_SH
106 if not retry:
107 op = op | fcntl.LOCK_NB
108
109 while True:
110 # If we leave the lockfiles lying around there is no problem
111 # but we should clean up after ourselves. This gives potential
112 # for races though. To work around this, when we acquire the lock
113 # we check the file we locked was still the lock file on disk.
114 # by comparing inode numbers. If they don't match or the lockfile
115 # no longer exists, we start again.
116
117 # This implementation is unfair since the last person to request the
118 # lock is the most likely to win it.
119
120 try:
121 lf = open(name, 'a+')
122 fileno = lf.fileno()
123 fcntl.flock(fileno, op)
124 statinfo = os.fstat(fileno)
125 if os.path.exists(lf.name):
126 statinfo2 = os.stat(lf.name)
127 if statinfo.st_ino == statinfo2.st_ino:
128 return lf
129 lf.close()
130 except Exception:
131 try:
132 lf.close()
133 except Exception:
134 pass
135 pass
136 if not retry:
137 return None
138
139def unlockfile(lf):
140 """
141 Unlock a file locked using lockfile()
142 """
143 try:
144 # If we had a shared lock, we need to promote to exclusive before
145 # removing the lockfile. Attempt this, ignore failures.
146 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
147 os.unlink(lf.name)
148 except (IOError, OSError):
149 pass
150 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
151 lf.close()
152
153def which(path, item, direction = 0, history = False):
154 """
155 Locate a file in a PATH
156 """
157
158 hist = []
159 paths = (path or "").split(':')
160 if direction != 0:
161 paths.reverse()
162
163 for p in paths:
164 next = os.path.join(p, item)
165 hist.append(next)
166 if os.path.exists(next):
167 if not os.path.isabs(next):
168 next = os.path.abspath(next)
169 if history:
170 return next, hist
171 return next
172
173 if history:
174 return "", hist
175 return ""
176
177
178
179# this can be used by all PM backends to create the index files in parallel
180def wic_create_index(arg):
181 index_cmd = arg
182
183 try:
184 msger.info("Executing '%s' ..." % index_cmd)
185 subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True)
186 except subprocess.CalledProcessError as e:
187 return("Index creation command '%s' failed with return code %d:\n%s" %
188 (e.cmd, e.returncode, e.output))
189
190 return None
191
192
193class WicIndexer(object):
194 __metaclass__ = ABCMeta
195
196 def __init__(self, d, deploy_dir):
197 self.d = d
198 self.deploy_dir = deploy_dir
199
200 @abstractmethod
201 def write_index(self):
202 pass
203
204class WicOpkgIndexer(WicIndexer):
205 def write_index(self):
206 arch_vars = ["ALL_MULTILIB_PACKAGE_ARCHS",
207 "SDK_PACKAGE_ARCHS",
208 "MULTILIB_ARCHS"]
209
210 opkg_index_cmd = which(os.getenv('PATH'), "opkg-make-index")
211
212 if not os.path.exists(os.path.join(self.deploy_dir, "Packages")):
213 open(os.path.join(self.deploy_dir, "Packages"), "w").close()
214
215 index_cmds = []
216 for arch_var in arch_vars:
217 if self.d.has_key(arch_var):
218 archs = self.d[arch_var]
219 else:
220 archs = None
221
222 if archs is None:
223 continue
224
225 for arch in archs.split():
226 pkgs_dir = os.path.join(self.deploy_dir, arch)
227 pkgs_file = os.path.join(pkgs_dir, "Packages")
228
229 if not os.path.isdir(pkgs_dir):
230 continue
231
232 if not os.path.exists(pkgs_file):
233 open(pkgs_file, "w").close()
234
235 index_cmds.append('%s -r %s -p %s -m %s' %
236 (opkg_index_cmd, pkgs_file, pkgs_file, pkgs_dir))
237
238 if len(index_cmds) == 0:
239 msger.info("There are no packages in %s!" % self.deploy_dir)
240 return
241
242 nproc = multiprocessing.cpu_count()
243 pool = multiprocessing.Pool(nproc)
244 results = list(pool.imap(wic_create_index, index_cmds))
245 pool.close()
246 pool.join()
247
248 for result in results:
249 if result is not None:
250 return(result)
251
252class WicPkgsList(object):
253 __metaclass__ = ABCMeta
254
255 def __init__(self, d, rootfs_dir):
256 self.d = d
257 self.rootfs_dir = rootfs_dir
258
259 @abstractmethod
260 def list(self, format=None):
261 pass
262
263
264class WicOpkgPkgsList(WicPkgsList):
265 def __init__(self, d, rootfs_dir, config_file):
266 super(WicOpkgPkgsList, self).__init__(d, rootfs_dir)
267
268 self.opkg_cmd = which(os.getenv('PATH'), "opkg-cl")
269 self.opkg_args = "-f %s -o %s " % (config_file, rootfs_dir)
270 if self.d.has_key("OPKG_ARGS"):
271 self.opkg_args += self.d["OPKG_ARGS"]
272
273 def list(self, format=None):
274 opkg_query_cmd = which(os.getenv('PATH'), "opkg-query-helper.py")
275
276 if format == "arch":
277 cmd = "%s %s status | %s -a" % \
278 (self.opkg_cmd, self.opkg_args, opkg_query_cmd)
279 elif format == "file":
280 cmd = "%s %s status | %s -f" % \
281 (self.opkg_cmd, self.opkg_args, opkg_query_cmd)
282 elif format == "ver":
283 cmd = "%s %s status | %s -v" % \
284 (self.opkg_cmd, self.opkg_args, opkg_query_cmd)
285 elif format == "deps":
286 cmd = "%s %s status | %s" % \
287 (self.opkg_cmd, self.opkg_args, opkg_query_cmd)
288 else:
289 cmd = "%s %s list_installed | cut -d' ' -f1" % \
290 (self.opkg_cmd, self.opkg_args)
291
292 try:
293 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).strip()
294 except subprocess.CalledProcessError as e:
295 msger.error("Cannot get the installed packages list. Command '%s' "
296 "returned %d:\n%s" % (cmd, e.returncode, e.output))
297
298 if output and format == "file":
299 tmp_output = ""
300 for line in output.split('\n'):
301 pkg, pkg_file, pkg_arch = line.split()
302 full_path = os.path.join(self.rootfs_dir, pkg_arch, pkg_file)
303 if os.path.exists(full_path):
304 tmp_output += "%s %s %s\n" % (pkg, full_path, pkg_arch)
305 else:
306 tmp_output += "%s %s %s\n" % (pkg, pkg_file, pkg_arch)
307
308 output = tmp_output
309
310 return output
311
312
313class WicPackageManager(object):
314 """
315 This is an abstract class. Do not instantiate this directly.
316 """
317 __metaclass__ = ABCMeta
318
319 def __init__(self, d, pseudo, native_sysroot):
320 self.d = d
321 self.deploy_dir = None
322 self.deploy_lock = None
323 if self.d.has_key('PACKAGE_FEED_URIS'):
324 self.feed_uris = self.d['PACKAGE_FEED_URIS']
325 else:
326 self.feed_uris = ""
327 self.pseudo = pseudo
328 self.native_sysroot = native_sysroot
329
330 """
331 Update the package manager package database.
332 """
333 @abstractmethod
334 def update(self):
335 pass
336
337 """
338 Install a list of packages. 'pkgs' is a list object. If 'attempt_only' is
339 True, installation failures are ignored.
340 """
341 @abstractmethod
342 def install(self, pkgs, attempt_only=False):
343 pass
344
345 """
346 Remove a list of packages. 'pkgs' is a list object. If 'with_dependencies'
347 is False, the any dependencies are left in place.
348 """
349 @abstractmethod
350 def remove(self, pkgs, with_dependencies=True):
351 pass
352
353 """
354 This function creates the index files
355 """
356 @abstractmethod
357 def write_index(self):
358 pass
359
360 @abstractmethod
361 def remove_packaging_data(self):
362 pass
363
364 @abstractmethod
365 def list_installed(self, format=None):
366 pass
367
368 @abstractmethod
369 def insert_feeds_uris(self):
370 pass
371
372 """
373 Install complementary packages based upon the list of currently installed
374 packages e.g. locales, *-dev, *-dbg, etc. This will only attempt to install
375 these packages, if they don't exist then no error will occur. Note: every
376 backend needs to call this function explicitly after the normal package
377 installation
378 """
379 def install_complementary(self, globs=None):
380 # we need to write the list of installed packages to a file because the
381 # oe-pkgdata-util reads it from a file
382 if self.d.has_key('WORKDIR'):
383 installed_pkgs_file = os.path.join(self.d['WORKDIR'],
384 "installed_pkgs.txt")
385 else:
386 msger.error("No WORKDIR provided!")
387
388 with open(installed_pkgs_file, "w+") as installed_pkgs:
389 installed_pkgs.write(self.list_installed("arch"))
390
391 if globs is None:
392 if self.d.has_key('IMAGE_INSTALL_COMPLEMENTARY'):
393 globs = self.d['IMAGE_INSTALL_COMPLEMENTARY']
394 split_linguas = set()
395
396 if self.d.has_key('IMAGE_LINGUAS'):
397 for translation in self.d['IMAGE_LINGUAS'].split():
398 split_linguas.add(translation)
399 split_linguas.add(translation.split('-')[0])
400
401 split_linguas = sorted(split_linguas)
402
403 for lang in split_linguas:
404 globs += " *-locale-%s" % lang
405
406 if globs is None:
407 return
408
409 if not self.d.has_key('PKGDATA_DIR'):
410 msger.error("No PKGDATA_DIR provided!")
411
412 cmd = [which(os.getenv('PATH'), "oe-pkgdata-util"),
413 "glob", self.d['PKGDATA_DIR'], installed_pkgs_file,
414 globs]
415
416 rc, out = exec_native_cmd(self.pseudo + cmd, self.native_sysroot)
417 if rc != 0:
418 msger.error("Could not compute complementary packages list. Command "
419 "'%s' returned %d" %
420 (' '.join(cmd), rc))
421
422 self.install(out.split(), attempt_only=True)
423
424
425 def deploy_dir_lock(self):
426 if self.deploy_dir is None:
427 raise RuntimeError("deploy_dir is not set!")
428
429 lock_file_name = os.path.join(self.deploy_dir, "deploy.lock")
430
431 self.deploy_lock = lockfile(lock_file_name)
432
433 def deploy_dir_unlock(self):
434 if self.deploy_lock is None:
435 return
436
437 unlockfile(self.deploy_lock)
438
439 self.deploy_lock = None
440
441
442class WicOpkgPM(WicPackageManager):
443 def __init__(self, d, target_rootfs, config_file, archs, pseudo, native_sysroot, task_name='target'):
444 super(WicOpkgPM, self).__init__(d, pseudo, native_sysroot)
445
446 self.target_rootfs = target_rootfs
447 self.config_file = config_file
448 self.pkg_archs = archs
449 self.task_name = task_name
450
451 if self.d.has_key("DEPLOY_DIR_IPK"):
452 self.deploy_dir = self.d["DEPLOY_DIR_IPK"]
453
454 self.deploy_lock_file = os.path.join(self.deploy_dir, "deploy.lock")
455 self.opkg_cmd = which(os.getenv('PATH'), "opkg-cl")
456 self.opkg_args = "-f %s -o %s " % (self.config_file, target_rootfs)
457 if self.d.has_key("OPKG_ARGS"):
458 self.opkg_args += self.d["OPKG_ARGS"]
459
460 if self.d.has_key('OPKGLIBDIR'):
461 opkg_lib_dir = self.d['OPKGLIBDIR']
462 else:
463 opkg_lib_dir = ""
464
465 if opkg_lib_dir[0] == "/":
466 opkg_lib_dir = opkg_lib_dir[1:]
467
468 self.opkg_dir = os.path.join(target_rootfs, opkg_lib_dir, "opkg")
469
470 mkdirhier(self.opkg_dir)
471
472 if self.d.has_key("TMPDIR"):
473 tmp_dir = self.d["TMPDIR"]
474 else:
475 tmp_dir = ""
476
477 self.saved_opkg_dir = '%s/saved/%s' % (tmp_dir, self.task_name)
478 if not os.path.exists('%s/saved' % tmp_dir):
479 mkdirhier('%s/saved' % tmp_dir)
480
481 if self.d.has_key('BUILD_IMAGES_FROM_FEEDS') and self.d['BUILD_IMAGES_FROM_FEEDS'] != "1":
482 self._create_config()
483 else:
484 self._create_custom_config()
485
486 self.indexer = WicOpkgIndexer(self.d, self.deploy_dir)
487
488 """
489 This function will change a package's status in /var/lib/opkg/status file.
490 If 'packages' is None then the new_status will be applied to all
491 packages
492 """
493 def mark_packages(self, status_tag, packages=None):
494 status_file = os.path.join(self.opkg_dir, "status")
495
496 with open(status_file, "r") as sf:
497 with open(status_file + ".tmp", "w+") as tmp_sf:
498 if packages is None:
499 tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
500 r"Package: \1\n\2Status: \3%s" % status_tag,
501 sf.read()))
502 else:
503 if type(packages).__name__ != "list":
504 raise TypeError("'packages' should be a list object")
505
506 status = sf.read()
507 for pkg in packages:
508 status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
509 r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
510 status)
511
512 tmp_sf.write(status)
513
514 os.rename(status_file + ".tmp", status_file)
515
516 def _create_custom_config(self):
517 msger.info("Building from feeds activated!")
518
519 with open(self.config_file, "w+") as config_file:
520 priority = 1
521 for arch in self.pkg_archs.split():
522 config_file.write("arch %s %d\n" % (arch, priority))
523 priority += 5
524
525 if self.d.has_key('IPK_FEED_URIS'):
526 ipk_feed_uris = self.d['IPK_FEED_URIS']
527 else:
528 ipk_feed_uris = ""
529
530 for line in ipk_feed_uris.split():
531 feed_match = re.match("^[ \t]*(.*)##([^ \t]*)[ \t]*$", line)
532
533 if feed_match is not None:
534 feed_name = feed_match.group(1)
535 feed_uri = feed_match.group(2)
536
537 msger.info("Add %s feed with URL %s" % (feed_name, feed_uri))
538
539 config_file.write("src/gz %s %s\n" % (feed_name, feed_uri))
540
541 """
542 Allow to use package deploy directory contents as quick devel-testing
543 feed. This creates individual feed configs for each arch subdir of those
544 specified as compatible for the current machine.
545 NOTE: Development-helper feature, NOT a full-fledged feed.
546 """
547 if self.d.has_key('FEED_DEPLOYDIR_BASE_URI'):
548 feed_deploydir_base_dir = self.d['FEED_DEPLOYDIR_BASE_URI']
549 else:
550 feed_deploydir_base_dir = ""
551
552 if feed_deploydir_base_dir != "":
553 for arch in self.pkg_archs.split():
554 if self.d.has_key("sysconfdir"):
555 sysconfdir = self.d["sysconfdir"]
556 else:
557 sysconfdir = None
558
559 cfg_file_name = os.path.join(self.target_rootfs,
560 sysconfdir,
561 "opkg",
562 "local-%s-feed.conf" % arch)
563
564 with open(cfg_file_name, "w+") as cfg_file:
565 cfg_file.write("src/gz local-%s %s/%s" %
566 arch,
567 feed_deploydir_base_dir,
568 arch)
569
570 def _create_config(self):
571 with open(self.config_file, "w+") as config_file:
572 priority = 1
573 for arch in self.pkg_archs.split():
574 config_file.write("arch %s %d\n" % (arch, priority))
575 priority += 5
576
577 config_file.write("src oe file:%s\n" % self.deploy_dir)
578
579 for arch in self.pkg_archs.split():
580 pkgs_dir = os.path.join(self.deploy_dir, arch)
581 if os.path.isdir(pkgs_dir):
582 config_file.write("src oe-%s file:%s\n" %
583 (arch, pkgs_dir))
584
585 def insert_feeds_uris(self):
586 if self.feed_uris == "":
587 return
588
589 rootfs_config = os.path.join('%s/etc/opkg/base-feeds.conf'
590 % self.target_rootfs)
591
592 with open(rootfs_config, "w+") as config_file:
593 uri_iterator = 0
594 for uri in self.feed_uris.split():
595 config_file.write("src/gz url-%d %s/ipk\n" %
596 (uri_iterator, uri))
597
598 for arch in self.pkg_archs.split():
599 if not os.path.exists(os.path.join(self.deploy_dir, arch)):
600 continue
601 msger.info('Note: adding opkg channel url-%s-%d (%s)' %
602 (arch, uri_iterator, uri))
603
604 config_file.write("src/gz uri-%s-%d %s/ipk/%s\n" %
605 (arch, uri_iterator, uri, arch))
606 uri_iterator += 1
607
608 def update(self):
609 self.deploy_dir_lock()
610
611 cmd = "%s %s update" % (self.opkg_cmd, self.opkg_args)
612
613 rc, out = exec_native_cmd(self.pseudo + cmd, self.native_sysroot)
614 if rc != 0:
615 self.deploy_dir_unlock()
616 msger.error("Unable to update the package index files. Command '%s' "
617 "returned %d" % (cmd, rc))
618
619 self.deploy_dir_unlock()
620
621 def install(self, pkgs, attempt_only=False):
622 if attempt_only and len(pkgs) == 0:
623 return
624
625 cmd = "%s %s install %s" % (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
626
627 os.environ['D'] = self.target_rootfs
628 os.environ['OFFLINE_ROOT'] = self.target_rootfs
629 os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
630 os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
631 if self.d.has_key('WORKDIR'):
632 os.environ['INTERCEPT_DIR'] = os.path.join(self.d['WORKDIR'],
633 "intercept_scripts")
634 else:
635 os.environ['INTERCEPT_DIR'] = "."
636 msger.warning("No WORKDIR provided!")
637
638 if self.d.has_key('STAGING_DIR_NATIVE'):
639 os.environ['NATIVE_ROOT'] = self.d['STAGING_DIR_NATIVE']
640 else:
641 msger.error("No STAGING_DIR_NATIVE provided!")
642
643 rc, out = exec_native_cmd(self.pseudo + cmd, self.native_sysroot)
644 if rc != 0:
645 msger.error("Unable to install packages. "
646 "Command '%s' returned %d" % (cmd, rc))
647
648
649 def remove(self, pkgs, with_dependencies=True):
650 if with_dependencies:
651 cmd = "%s %s --force-depends --force-remove --force-removal-of-dependent-packages remove %s" % \
652 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
653 else:
654 cmd = "%s %s --force-depends remove %s" % \
655 (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
656
657 rc, out = exec_native_cmd(self.pseudo + cmd, self.native_sysroot)
658 if rc != 0:
659 msger.error("Unable to remove packages. Command '%s' "
660 "returned %d" % (cmd, rc))
661
662
663 def write_index(self):
664 self.deploy_dir_lock()
665
666 result = self.indexer.write_index()
667
668 self.deploy_dir_unlock()
669
670 if result is not None:
671 msger.error(result)
672
673 def remove_packaging_data(self):
674 remove(self.opkg_dir, True)
675 # create the directory back, it's needed by PM lock
676 mkdirhier(self.opkg_dir)
677
678 def list_installed(self, format=None):
679 return WicOpkgPkgsList(self.d, self.target_rootfs, self.config_file).list(format)
680
681 def handle_bad_recommendations(self):
682 if self.d.has_key("BAD_RECOMMENDATIONS"):
683 bad_recommendations = self.d["BAD_RECOMMENDATIONS"]
684 else:
685 bad_recommendations = ""
686
687 if bad_recommendations.strip() == "":
688 return
689
690 status_file = os.path.join(self.opkg_dir, "status")
691
692 # If status file existed, it means the bad recommendations has already
693 # been handled
694 if os.path.exists(status_file):
695 return
696
697 cmd = "%s %s info " % (self.opkg_cmd, self.opkg_args)
698
699 with open(status_file, "w+") as status:
700 for pkg in bad_recommendations.split():
701 pkg_info = cmd + pkg
702
703 try:
704 output = subprocess.check_output(pkg_info.split(), stderr=subprocess.STDOUT).strip()
705 except subprocess.CalledProcessError as e:
706 msger.error("Cannot get package info. Command '%s' "
707 "returned %d:\n%s" % (pkg_info, e.returncode, e.output))
708
709 if output == "":
710 msger.info("Ignored bad recommendation: '%s' is "
711 "not a package" % pkg)
712 continue
713
714 for line in output.split('\n'):
715 if line.startswith("Status:"):
716 status.write("Status: deinstall hold not-installed\n")
717 else:
718 status.write(line + "\n")
719
720 '''
721 The following function dummy installs pkgs and returns the log of output.
722 '''
723 def dummy_install(self, pkgs):
724 if len(pkgs) == 0:
725 return
726
727 # Create an temp dir as opkg root for dummy installation
728 if self.d.has_key("TMPDIR"):
729 tmp_dir = self.d["TMPDIR"]
730 else:
731 tmp_dir = "."
732 msger.warning("No TMPDIR provided!")
733
734 temp_rootfs = '%s/opkg' % tmp_dir
735 temp_opkg_dir = os.path.join(temp_rootfs, 'var/lib/opkg')
736 mkdirhier(temp_opkg_dir)
737
738 opkg_args = "-f %s -o %s " % (self.config_file, temp_rootfs)
739 if self.d.has_key("OPKG_ARGS"):
740 opkg_args += self.d["OPKG_ARGS"]
741
742 cmd = "%s %s update" % (self.opkg_cmd, opkg_args)
743 try:
744 subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
745 except subprocess.CalledProcessError as e:
746 msger.error("Unable to update. Command '%s' "
747 "returned %d:\n%s" % (cmd, e.returncode, e.output))
748
749 # Dummy installation
750 cmd = "%s %s --noaction install %s " % (self.opkg_cmd,
751 opkg_args,
752 ' '.join(pkgs))
753 try:
754 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
755 except subprocess.CalledProcessError as e:
756 msger.error("Unable to dummy install packages. Command '%s' "
757 "returned %d:\n%s" % (cmd, e.returncode, e.output))
758
759 remove(temp_rootfs, True)
760
761 return output
762
763 def backup_packaging_data(self):
764 # Save the opkglib for increment ipk image generation
765 if os.path.exists(self.saved_opkg_dir):
766 remove(self.saved_opkg_dir, True)
767 shutil.copytree(self.opkg_dir,
768 self.saved_opkg_dir,
769 symlinks=True)
770
771 def recover_packaging_data(self):
772 # Move the opkglib back
773 if os.path.exists(self.saved_opkg_dir):
774 if os.path.exists(self.opkg_dir):
775 remove(self.opkg_dir, True)
776
777 msger.info('Recover packaging data')
778 shutil.copytree(self.saved_opkg_dir,
779 self.opkg_dir,
780 symlinks=True)
781
782
783def wic_generate_index_files(d):
784 if d.has_key('PACKAGE_CLASSES'):
785 classes = d['PACKAGE_CLASSES'].replace("package_", "").split()
786 else:
787 classes = ""
788 msger.warning("No PACKAGE_CLASSES provided!")
789
790 if d.has_key('DEPLOY_DIR_IPK'):
791 deploy_dir_ipk = d['DEPLOY_DIR_IPK']
792 else:
793 deploy_dir_ipk = None
794 msger.warning("No DEPLOY_DIR_IPK provided!")
795
796 indexer_map = {
797 "ipk": (WicOpkgIndexer, deploy_dir_ipk)
798 }
799
800 result = None
801
802 for pkg_class in classes:
803 if not pkg_class in indexer_map:
804 continue
805
806 if os.path.exists(indexer_map[pkg_class][1]):
807 result = indexer_map[pkg_class][0](d, indexer_map[pkg_class][1]).write_index()
808
809 if result is not None:
810 msger.error(result)
diff --git a/scripts/lib/wic/utils/partitionedfs.py b/scripts/lib/wic/utils/partitionedfs.py
new file mode 100644
index 0000000000..fb95cc790e
--- /dev/null
+++ b/scripts/lib/wic/utils/partitionedfs.py
@@ -0,0 +1,360 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2009, 2010, 2011 Intel, Inc.
4# Copyright (c) 2007, 2008 Red Hat, Inc.
5# Copyright (c) 2008 Daniel P. Berrange
6# Copyright (c) 2008 David P. Huff
7#
8# This program is free software; you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by the Free
10# Software Foundation; version 2 of the License
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15# for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc., 59
19# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21import os
22
23from wic import msger
24from wic.utils import runner
25from wic.utils.errors import ImageError
26from wic.utils.fs_related import *
27from wic.utils.oe.misc import *
28
29# Overhead of the MBR partitioning scheme (just one sector)
30MBR_OVERHEAD = 1
31
32# Size of a sector in bytes
33SECTOR_SIZE = 512
34
35class Image:
36 """
37 Generic base object for an image.
38
39 An Image is a container for a set of DiskImages and associated
40 partitions.
41 """
42 def __init__(self):
43 self.disks = {}
44 self.partitions = []
45 self.parted = find_binary_path("parted")
46 # Size of a sector used in calculations
47 self.sector_size = SECTOR_SIZE
48 self._partitions_layed_out = False
49
50 def __add_disk(self, disk_name):
51 """ Add a disk 'disk_name' to the internal list of disks. Note,
52 'disk_name' is the name of the disk in the target system
53 (e.g., sdb). """
54
55 if disk_name in self.disks:
56 # We already have this disk
57 return
58
59 assert not self._partitions_layed_out
60
61 self.disks[disk_name] = \
62 { 'disk': None, # Disk object
63 'numpart': 0, # Number of allocate partitions
64 'partitions': [], # Indexes to self.partitions
65 'offset': 0, # Offset of next partition (in sectors)
66 # Minimum required disk size to fit all partitions (in bytes)
67 'min_size': 0,
68 'ptable_format': "msdos" } # Partition table format
69
70 def add_disk(self, disk_name, disk_obj):
71 """ Add a disk object which have to be partitioned. More than one disk
72 can be added. In case of multiple disks, disk partitions have to be
73 added for each disk separately with 'add_partition()". """
74
75 self.__add_disk(disk_name)
76 self.disks[disk_name]['disk'] = disk_obj
77
78 def __add_partition(self, part):
79 """ This is a helper function for 'add_partition()' which adds a
80 partition to the internal list of partitions. """
81
82 assert not self._partitions_layed_out
83
84 self.partitions.append(part)
85 self.__add_disk(part['disk_name'])
86
87 def add_partition(self, size, disk_name, mountpoint, source_file = None, fstype = None,
88 label=None, fsopts = None, boot = False, align = None,
89 part_type = None):
90 """ Add the next partition. Prtitions have to be added in the
91 first-to-last order. """
92
93 ks_pnum = len(self.partitions)
94
95 # Converting MB to sectors for parted
96 size = size * 1024 * 1024 / self.sector_size
97
98 # We still need partition for "/" or non-subvolume
99 if mountpoint == "/" or not fsopts:
100 part = { 'ks_pnum' : ks_pnum, # Partition number in the KS file
101 'size': size, # In sectors
102 'mountpoint': mountpoint, # Mount relative to chroot
103 'source_file': source_file, # partition contents
104 'fstype': fstype, # Filesystem type
105 'fsopts': fsopts, # Filesystem mount options
106 'label': label, # Partition label
107 'disk_name': disk_name, # physical disk name holding partition
108 'device': None, # kpartx device node for partition
109 'num': None, # Partition number
110 'boot': boot, # Bootable flag
111 'align': align, # Partition alignment
112 'part_type' : part_type } # Partition type
113
114 self.__add_partition(part)
115
116 def layout_partitions(self, ptable_format = "msdos"):
117 """ Layout the partitions, meaning calculate the position of every
118 partition on the disk. The 'ptable_format' parameter defines the
119 partition table format and may be "msdos". """
120
121 msger.debug("Assigning %s partitions to disks" % ptable_format)
122
123 if ptable_format not in ('msdos'):
124 raise ImageError("Unknown partition table format '%s', supported " \
125 "formats are: 'msdos'" % ptable_format)
126
127 if self._partitions_layed_out:
128 return
129
130 self._partitions_layed_out = True
131
132 # Go through partitions in the order they are added in .ks file
133 for n in range(len(self.partitions)):
134 p = self.partitions[n]
135
136 if not self.disks.has_key(p['disk_name']):
137 raise ImageError("No disk %s for partition %s" \
138 % (p['disk_name'], p['mountpoint']))
139
140 if p['part_type']:
141 # The --part-type can also be implemented for MBR partitions,
142 # in which case it would map to the 1-byte "partition type"
143 # filed at offset 3 of the partition entry.
144 raise ImageError("setting custom partition type is not " \
145 "implemented for msdos partitions")
146
147 # Get the disk where the partition is located
148 d = self.disks[p['disk_name']]
149 d['numpart'] += 1
150 d['ptable_format'] = ptable_format
151
152 if d['numpart'] == 1:
153 if ptable_format == "msdos":
154 overhead = MBR_OVERHEAD
155
156 # Skip one sector required for the partitioning scheme overhead
157 d['offset'] += overhead
158
159 if p['align']:
160 # If not first partition and we do have alignment set we need
161 # to align the partition.
162 # FIXME: This leaves a empty spaces to the disk. To fill the
163 # gaps we could enlargea the previous partition?
164
165 # Calc how much the alignment is off.
166 align_sectors = d['offset'] % (p['align'] * 1024 / self.sector_size)
167
168 if align_sectors:
169 # If partition is not aligned as required, we need
170 # to move forward to the next alignment point
171 align_sectors = (p['align'] * 1024 / self.sector_size) - align_sectors
172
173 msger.debug("Realignment for %s%s with %s sectors, original"
174 " offset %s, target alignment is %sK." %
175 (p['disk_name'], d['numpart'], align_sectors,
176 d['offset'], p['align']))
177
178 # increase the offset so we actually start the partition on right alignment
179 d['offset'] += align_sectors
180
181 p['start'] = d['offset']
182 d['offset'] += p['size']
183
184 p['type'] = 'primary'
185 p['num'] = d['numpart']
186
187 if d['ptable_format'] == "msdos":
188 if d['numpart'] > 2:
189 # Every logical partition requires an additional sector for
190 # the EBR, so steal the last sector from the end of each
191 # partition starting from the 3rd one for the EBR. This
192 # will make sure the logical partitions are aligned
193 # correctly.
194 p['size'] -= 1
195
196 if d['numpart'] > 3:
197 p['type'] = 'logical'
198 p['num'] = d['numpart'] + 1
199
200 d['partitions'].append(n)
201 msger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
202 "sectors (%d bytes)." \
203 % (p['mountpoint'], p['disk_name'], p['num'],
204 p['start'], p['start'] + p['size'] - 1,
205 p['size'], p['size'] * self.sector_size))
206
207 # Once all the partitions have been layed out, we can calculate the
208 # minumim disk sizes.
209 for disk_name, d in self.disks.items():
210 d['min_size'] = d['offset']
211
212 d['min_size'] *= self.sector_size
213
214 def __run_parted(self, args):
215 """ Run parted with arguments specified in the 'args' list. """
216
217 args.insert(0, self.parted)
218 msger.debug(args)
219
220 rc, out = runner.runtool(args, catch = 3)
221 out = out.strip()
222 if out:
223 msger.debug('"parted" output: %s' % out)
224
225 if rc != 0:
226 # We don't throw exception when return code is not 0, because
227 # parted always fails to reload part table with loop devices. This
228 # prevents us from distinguishing real errors based on return
229 # code.
230 msger.error("WARNING: parted returned '%s' instead of 0 (use --debug for details)" % rc)
231
232 def __create_partition(self, device, parttype, fstype, start, size):
233 """ Create a partition on an image described by the 'device' object. """
234
235 # Start is included to the size so we need to substract one from the end.
236 end = start + size - 1
237 msger.debug("Added '%s' partition, sectors %d-%d, size %d sectors" %
238 (parttype, start, end, size))
239
240 args = ["-s", device, "unit", "s", "mkpart", parttype]
241 if fstype:
242 args.extend([fstype])
243 args.extend(["%d" % start, "%d" % end])
244
245 return self.__run_parted(args)
246
247 def __format_disks(self):
248 self.layout_partitions()
249
250 for dev in self.disks.keys():
251 d = self.disks[dev]
252 msger.debug("Initializing partition table for %s" % \
253 (d['disk'].device))
254 self.__run_parted(["-s", d['disk'].device, "mklabel",
255 d['ptable_format']])
256
257 msger.debug("Creating partitions")
258
259 for p in self.partitions:
260 d = self.disks[p['disk_name']]
261 if d['ptable_format'] == "msdos" and p['num'] == 5:
262 # The last sector of the 3rd partition was reserved for the EBR
263 # of the first _logical_ partition. This is why the extended
264 # partition should start one sector before the first logical
265 # partition.
266 self.__create_partition(d['disk'].device, "extended",
267 None, p['start'] - 1,
268 d['offset'] - p['start'])
269
270 if p['fstype'] == "swap":
271 parted_fs_type = "linux-swap"
272 elif p['fstype'] == "vfat":
273 parted_fs_type = "fat32"
274 elif p['fstype'] == "msdos":
275 parted_fs_type = "fat16"
276 else:
277 # Type for ext2/ext3/ext4/btrfs
278 parted_fs_type = "ext2"
279
280 # Boot ROM of OMAP boards require vfat boot partition to have an
281 # even number of sectors.
282 if p['mountpoint'] == "/boot" and p['fstype'] in ["vfat", "msdos"] \
283 and p['size'] % 2:
284 msger.debug("Substracting one sector from '%s' partition to " \
285 "get even number of sectors for the partition" % \
286 p['mountpoint'])
287 p['size'] -= 1
288
289 self.__create_partition(d['disk'].device, p['type'],
290 parted_fs_type, p['start'], p['size'])
291
292 if p['boot']:
293 flag_name = "boot"
294 msger.debug("Set '%s' flag for partition '%s' on disk '%s'" % \
295 (flag_name, p['num'], d['disk'].device))
296 self.__run_parted(["-s", d['disk'].device, "set",
297 "%d" % p['num'], flag_name, "on"])
298
299 # Parted defaults to enabling the lba flag for fat16 partitions,
300 # which causes compatibility issues with some firmware (and really
301 # isn't necessary).
302 if parted_fs_type == "fat16":
303 if d['ptable_format'] == 'msdos':
304 msger.debug("Disable 'lba' flag for partition '%s' on disk '%s'" % \
305 (p['num'], d['disk'].device))
306 self.__run_parted(["-s", d['disk'].device, "set",
307 "%d" % p['num'], "lba", "off"])
308
309 def cleanup(self):
310 if self.disks:
311 for dev in self.disks.keys():
312 d = self.disks[dev]
313 try:
314 d['disk'].cleanup()
315 except:
316 pass
317
318 def __write_partition(self, num, source_file, start, size):
319 """
320 Install source_file contents into a partition.
321 """
322 if not source_file: # nothing to write
323 return
324
325 # Start is included in the size so need to substract one from the end.
326 end = start + size - 1
327 msger.debug("Installed %s in partition %d, sectors %d-%d, size %d sectors" % (source_file, num, start, end, size))
328
329 dd_cmd = "dd if=%s of=%s bs=%d seek=%d count=%d conv=notrunc" % \
330 (source_file, self.image_file, self.sector_size, start, size)
331 exec_cmd(dd_cmd)
332
333
334 def assemble(self, image_file):
335 msger.debug("Installing partitions")
336
337 self.image_file = image_file
338
339 for p in self.partitions:
340 d = self.disks[p['disk_name']]
341 if d['ptable_format'] == "msdos" and p['num'] == 5:
342 # The last sector of the 3rd partition was reserved for the EBR
343 # of the first _logical_ partition. This is why the extended
344 # partition should start one sector before the first logical
345 # partition.
346 self.__write_partition(p['num'], p['source_file'],
347 p['start'] - 1,
348 d['offset'] - p['start'])
349
350 self.__write_partition(p['num'], p['source_file'],
351 p['start'], p['size'])
352
353 def create(self):
354 for dev in self.disks.keys():
355 d = self.disks[dev]
356 d['disk'].create()
357
358 self.__format_disks()
359
360 return
diff --git a/scripts/lib/wic/utils/runner.py b/scripts/lib/wic/utils/runner.py
new file mode 100644
index 0000000000..2ae9f417c5
--- /dev/null
+++ b/scripts/lib/wic/utils/runner.py
@@ -0,0 +1,109 @@
1#!/usr/bin/env python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import subprocess
20
21from wic import msger
22
23def runtool(cmdln_or_args, catch=1):
24 """ wrapper for most of the subprocess calls
25 input:
26 cmdln_or_args: can be both args and cmdln str (shell=True)
27 catch: 0, quitely run
28 1, only STDOUT
29 2, only STDERR
30 3, both STDOUT and STDERR
31 return:
32 (rc, output)
33 if catch==0: the output will always None
34 """
35
36 if catch not in (0, 1, 2, 3):
37 # invalid catch selection, will cause exception, that's good
38 return None
39
40 if isinstance(cmdln_or_args, list):
41 cmd = cmdln_or_args[0]
42 shell = False
43 else:
44 import shlex
45 cmd = shlex.split(cmdln_or_args)[0]
46 shell = True
47
48 if catch != 3:
49 dev_null = os.open("/dev/null", os.O_WRONLY)
50
51 if catch == 0:
52 sout = dev_null
53 serr = dev_null
54 elif catch == 1:
55 sout = subprocess.PIPE
56 serr = dev_null
57 elif catch == 2:
58 sout = dev_null
59 serr = subprocess.PIPE
60 elif catch == 3:
61 sout = subprocess.PIPE
62 serr = subprocess.STDOUT
63
64 try:
65 p = subprocess.Popen(cmdln_or_args, stdout=sout,
66 stderr=serr, shell=shell)
67 (sout, serr) = p.communicate()
68 # combine stdout and stderr, filter None out
69 out = ''.join(filter(None, [sout, serr]))
70 except OSError, e:
71 if e.errno == 2:
72 # [Errno 2] No such file or directory
73 msger.error('Cannot run command: %s, lost dependency?' % cmd)
74 else:
75 raise # relay
76 finally:
77 if catch != 3:
78 os.close(dev_null)
79
80 return (p.returncode, out)
81
82def show(cmdln_or_args):
83 # show all the message using msger.verbose
84
85 rc, out = runtool(cmdln_or_args, catch=3)
86
87 if isinstance(cmdln_or_args, list):
88 cmd = ' '.join(cmdln_or_args)
89 else:
90 cmd = cmdln_or_args
91
92 msg = 'running command: "%s"' % cmd
93 if out: out = out.strip()
94 if out:
95 msg += ', with output::'
96 msg += '\n +----------------'
97 for line in out.splitlines():
98 msg += '\n | %s' % line
99 msg += '\n +----------------'
100
101 msger.verbose(msg)
102 return rc
103
104def outs(cmdln_or_args, catch=1):
105 # get the outputs of tools
106 return runtool(cmdln_or_args, catch)[1].strip()
107
108def quiet(cmdln_or_args):
109 return runtool(cmdln_or_args, catch=0)[0]