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.
38 def __init__(self, creatoropts = None, pkgmgr = None, extra_loop = {}):
39 """Initialize a LoopImageCreator instance.
41 This method takes the same arguments as ImageCreator.__init__() with
44 fslabel -- A string used as a label for any filesystems created.
46 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
49 self.name = misc.strip_end(os.path.basename(extra_loop['/']), '.img')
51 self.extra_loop = extra_loop
54 self.fslabel = self.name
56 self.__blocksize = 4096
58 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
59 self.__fsopts = kickstart.get_image_fsopts(self.ks, "defaults,noatime")
64 self._instloops = [] # list of dict of image_name:loop_device
69 self.__image_size = kickstart.get_image_size(self.ks,
74 self._img_name = self.name + ".img"
77 def _set_fstype(self, fstype):
78 self.__fstype = fstype
80 def _set_image_size(self, imgsize):
81 self.__image_size = imgsize
87 def __get_fslabel(self):
88 if self.__fslabel is None:
92 def __set_fslabel(self, val):
96 self.__fslabel = val[:FSLABEL_MAXLEN]
97 #A string used to label any filesystems created.
99 #Some filesystems impose a constraint on the maximum allowed size of the
100 #filesystem label. In the case of ext3 it's 16 characters, but in the case
101 #of ISO9660 it's 32 characters.
103 #mke2fs silently truncates the label, but mkisofs aborts if the label is too
104 #long. So, for convenience sake, any string assigned to this attribute is
105 #silently truncated to FSLABEL_MAXLEN (32) characters.
106 fslabel = property(__get_fslabel, __set_fslabel)
108 def __get_image(self):
109 if self.__imgdir is None:
110 raise CreatorError("_image is not valid before calling mount()")
111 return os.path.join(self.__imgdir, self._img_name)
112 #The location of the image file.
114 #This is the path to the filesystem image. Subclasses may use this path
115 #in order to package the image in _stage_final_image().
117 #Note, this directory does not exist before ImageCreator.mount() is called.
119 #Note also, this is a read-only attribute.
120 _image = property(__get_image)
122 def __get_blocksize(self):
123 return self.__blocksize
124 def __set_blocksize(self, val):
126 raise CreatorError("_blocksize must be set before calling mount()")
128 self.__blocksize = int(val)
130 raise CreatorError("'%s' is not a valid integer value "
131 "for _blocksize" % val)
132 #The block size used by the image's filesystem.
134 #This is the block size used when creating the filesystem image. Subclasses
135 #may change this if they wish to use something other than a 4k block size.
137 #Note, this attribute may only be set before calling mount().
138 _blocksize = property(__get_blocksize, __set_blocksize)
140 def __get_fstype(self):
142 def __set_fstype(self, val):
143 if val != "ext2" and val != "ext3":
144 raise CreatorError("Unknown _fstype '%s' supplied" % val)
146 #The type of filesystem used for the image.
148 #This is the filesystem type used when creating the filesystem image.
149 #Subclasses may change this if they wish to use something other ext3.
151 #Note, only ext2 and ext3 are currently supported.
153 #Note also, this attribute may only be set before calling mount().
154 _fstype = property(__get_fstype, __set_fstype)
156 def __get_fsopts(self):
158 def __set_fsopts(self, val):
160 #Mount options of filesystem used for the image.
162 #This can be specified by --fsoptions="xxx,yyy" in part command in
164 _fsopts = property(__get_fsopts, __set_fsopts)
168 # Helpers for subclasses
170 def _resparse(self, size = None):
171 """Rebuild the filesystem image to be as sparse as possible.
173 This method should be used by subclasses when staging the final image
174 in order to reduce the actual space taken up by the sparse image file
175 to be as little as possible.
177 This is done by resizing the filesystem to the minimal size (thereby
178 eliminating any space taken up by deleted files) and then resizing it
179 back to the supplied size.
181 size -- the size in, in bytes, which the filesystem image should be
182 resized to after it has been minimized; this defaults to None,
183 causing the original size specified by the kickstart file to
184 be used (or 4GiB if not specified in the kickstart).
187 for item in self._instloops:
188 if item['name'] == self._img_name:
189 minsize = item['loop'].resparse(size)
191 item['loop'].resparse(size)
195 def _base_on(self, base_on):
196 if base_on is not None:
197 shutil.copyfile(base_on, self._image)
199 def _check_imgdir(self):
200 if self.__imgdir is None:
201 self.__imgdir = self._mkdtemp()
204 # Actual implementation
206 def _mount_instroot(self, base_on = None):
208 self._base_on(base_on)
210 if self.__fstype in ("ext2", "ext3", "ext4"):
211 MyDiskMount = fs.ExtDiskMount
212 elif self.__fstype == "btrfs":
213 MyDiskMount = fs.BtrfsDiskMount
215 self._instloops.append({
216 'name': self._img_name,
217 'loop': MyDiskMount(fs.SparseLoopbackDisk(self._image, self.__image_size),
224 for point, name in self.extra_loop.iteritems():
225 if name != os.path.basename(name):
226 msger.warning('can not specify path in %s' % name)
227 name = os.path.basename(name)
228 imgname = misc.strip_end(name,'.img') + '.img'
229 if point.startswith('/'):
230 point = point.lstrip('/')
232 self._instloops.append({
234 'loop': MyDiskMount(fs.SparseLoopbackDisk(os.path.join(self.__imgdir, imgname),
236 os.path.join(self._instroot, point),
242 for item in self._instloops:
244 msger.verbose('Mounting image "%s" on "%s"' %(item['name'], item['loop'].mountdir))
245 fs.makedirs(item['loop'].mountdir)
247 except MountError, e:
250 def _unmount_instroot(self):
251 for item in reversed(self._instloops):
252 item['loop'].cleanup()
254 def _stage_final_image(self):
256 for item in self._instloops:
257 shutil.move(os.path.join(self.__imgdir, item['name']),
258 os.path.join(self._outdir, item['name']))