3 # Copyright (c) 2011 Intel, Inc.
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
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
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.
21 from mic import kickstart, msger
22 from mic.utils.errors import CreatorError, MountError
23 from mic.utils import misc, fs_related as fs
25 from baseimager import BaseImageCreator
28 """The maximum string length supported for LoopImageCreator.fslabel."""
30 class LoopImageCreator(BaseImageCreator):
31 """Installs a system into a loopback-mountable filesystem image.
33 LoopImageCreator is a straightforward ImageCreator subclass; the system
34 is installed into an ext3 filesystem on a sparse file which can be
35 subsequently loopback-mounted.
37 When specifying multiple partitions in kickstart file, each partition
38 will be created as a separated loop image.
41 def __init__(self, creatoropts = None, pkgmgr = None):
42 """Initialize a LoopImageCreator instance.
44 This method takes the same arguments as ImageCreator.__init__() with
47 fslabel -- A string used as a label for any filesystems created.
49 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
52 self.fslabel = self.name
54 self.__blocksize = 4096
56 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
57 self.__fsopts = kickstart.get_image_fsopts(self.ks, "defaults,noatime")
60 for part in sorted(kickstart.get_partitions(self.ks),
61 key = lambda p: p.mountpoint):
72 msger.warning('no "label" specified for loop img at %s, use the mountpoint as the name' % mp)
73 label = mp.split('/')[-1]
75 imgname = misc.strip_end(label,'.img') + '.img'
80 'size': part.size or 4096L * 1024 * 1024,
81 'fstype': part.fstype or 'ext4',
82 'loop': None, # to be created in _mount_instroot
84 self._instloops = allloops
94 self.__image_size = kickstart.get_image_size(self.ks,
99 self._img_name = self.name + ".img"
102 def _set_fstype(self, fstype):
103 self.__fstype = fstype
105 def _set_image_size(self, imgsize):
106 self.__image_size = imgsize
112 def __get_fslabel(self):
113 if self.__fslabel is None:
116 return self.__fslabel
117 def __set_fslabel(self, val):
119 self.__fslabel = None
121 self.__fslabel = val[:FSLABEL_MAXLEN]
122 #A string used to label any filesystems created.
124 #Some filesystems impose a constraint on the maximum allowed size of the
125 #filesystem label. In the case of ext3 it's 16 characters, but in the case
126 #of ISO9660 it's 32 characters.
128 #mke2fs silently truncates the label, but mkisofs aborts if the label is too
129 #long. So, for convenience sake, any string assigned to this attribute is
130 #silently truncated to FSLABEL_MAXLEN (32) characters.
131 fslabel = property(__get_fslabel, __set_fslabel)
133 def __get_image(self):
134 if self.__imgdir is None:
135 raise CreatorError("_image is not valid before calling mount()")
136 return os.path.join(self.__imgdir, self._img_name)
137 #The location of the image file.
139 #This is the path to the filesystem image. Subclasses may use this path
140 #in order to package the image in _stage_final_image().
142 #Note, this directory does not exist before ImageCreator.mount() is called.
144 #Note also, this is a read-only attribute.
145 _image = property(__get_image)
147 def __get_blocksize(self):
148 return self.__blocksize
149 def __set_blocksize(self, val):
151 raise CreatorError("_blocksize must be set before calling mount()")
153 self.__blocksize = int(val)
155 raise CreatorError("'%s' is not a valid integer value "
156 "for _blocksize" % val)
157 #The block size used by the image's filesystem.
159 #This is the block size used when creating the filesystem image. Subclasses
160 #may change this if they wish to use something other than a 4k block size.
162 #Note, this attribute may only be set before calling mount().
163 _blocksize = property(__get_blocksize, __set_blocksize)
165 def __get_fstype(self):
167 def __set_fstype(self, val):
168 if val != "ext2" and val != "ext3":
169 raise CreatorError("Unknown _fstype '%s' supplied" % val)
171 #The type of filesystem used for the image.
173 #This is the filesystem type used when creating the filesystem image.
174 #Subclasses may change this if they wish to use something other ext3.
176 #Note, only ext2 and ext3 are currently supported.
178 #Note also, this attribute may only be set before calling mount().
179 _fstype = property(__get_fstype, __set_fstype)
181 def __get_fsopts(self):
183 def __set_fsopts(self, val):
185 #Mount options of filesystem used for the image.
187 #This can be specified by --fsoptions="xxx,yyy" in part command in
189 _fsopts = property(__get_fsopts, __set_fsopts)
193 # Helpers for subclasses
195 def _resparse(self, size = None):
196 """Rebuild the filesystem image to be as sparse as possible.
198 This method should be used by subclasses when staging the final image
199 in order to reduce the actual space taken up by the sparse image file
200 to be as little as possible.
202 This is done by resizing the filesystem to the minimal size (thereby
203 eliminating any space taken up by deleted files) and then resizing it
204 back to the supplied size.
206 size -- the size in, in bytes, which the filesystem image should be
207 resized to after it has been minimized; this defaults to None,
208 causing the original size specified by the kickstart file to
209 be used (or 4GiB if not specified in the kickstart).
212 for item in self._instloops:
213 if item['name'] == self._img_name:
214 minsize = item['loop'].resparse(size)
216 item['loop'].resparse(size)
220 def _base_on(self, base_on):
221 if base_on is not None:
222 shutil.copyfile(base_on, self._image)
224 def _check_imgdir(self):
225 if self.__imgdir is None:
226 self.__imgdir = self._mkdtemp()
229 # Actual implementation
231 def _mount_instroot(self, base_on = None):
233 self._base_on(base_on)
234 imgdir = os.path.dirname(self._image)
236 for loop in self._instloops:
237 fstype = loop['fstype']
238 mp = os.path.join(self._instroot, loop['mountpoint'].lstrip('/'))
239 size = loop['size'] * 1024L * 1024L
240 imgname = loop['name']
242 if fstype in ("ext2", "ext3", "ext4"):
243 MyDiskMount = fs.ExtDiskMount
244 elif fstype == "btrfs":
245 MyDiskMount = fs.BtrfsDiskMount
246 elif fstype in ("vfat", "msdos"):
247 MyDiskMount = fs.VfatDiskMount
249 msger.error('Cannot support fstype: %s' % fstype)
251 loop['loop'] = MyDiskMount(fs.SparseLoopbackDisk(os.path.join(imgdir, imgname), size),
258 msger.verbose('Mounting image "%s" on "%s"' %(imgname, mp))
261 except errors.MountError, e:
264 def _unmount_instroot(self):
265 for item in reversed(self._instloops):
266 item['loop'].cleanup()
268 def _stage_final_image(self):
270 for item in self._instloops:
271 shutil.move(os.path.join(self.__imgdir, item['name']),
272 os.path.join(self._outdir, item['name']))