unify the license header of all files
[tools/mic.git] / mic / imager / loop.py
1 #!/usr/bin/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
18 import os
19 import shutil
20
21 from mic import kickstart, msger
22 from mic.utils.errors import CreatorError, MountError
23 from mic.utils import misc, fs_related as fs
24
25 from baseimager import BaseImageCreator
26
27 FSLABEL_MAXLEN = 32
28 """The maximum string length supported for LoopImageCreator.fslabel."""
29
30 class LoopImageCreator(BaseImageCreator):
31     """Installs a system into a loopback-mountable filesystem image.
32
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.
36     """
37
38     def __init__(self, creatoropts = None, pkgmgr = None, extra_loop = {}):
39         """Initialize a LoopImageCreator instance.
40
41             This method takes the same arguments as ImageCreator.__init__() with
42             the addition of:
43
44             fslabel -- A string used as a label for any filesystems created.
45         """
46         BaseImageCreator.__init__(self, creatoropts, pkgmgr)
47
48         if '/' in extra_loop:
49             self.name = misc.strip_end(os.path.basename(extra_loop['/']), '.img')
50             del extra_loop['/']
51         self.extra_loop = extra_loop
52
53         self.__fslabel = None
54         self.fslabel = self.name
55
56         self.__blocksize = 4096
57         if self.ks:
58             self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
59             self.__fsopts = kickstart.get_image_fsopts(self.ks, "defaults,noatime")
60         else:
61             self.__fstype = None
62             self.__fsopts = None
63
64         self._instloops = [] # list of dict of image_name:loop_device
65
66         self.__imgdir = None
67
68         if self.ks:
69             self.__image_size = kickstart.get_image_size(self.ks,
70                                                          4096L * 1024 * 1024)
71         else:
72             self.__image_size = 0
73
74         self._img_name = self.name + ".img"
75
76
77     def _set_fstype(self, fstype):
78         self.__fstype = fstype
79
80     def _set_image_size(self, imgsize):
81         self.__image_size = imgsize
82
83
84     #
85     # Properties
86     #
87     def __get_fslabel(self):
88         if self.__fslabel is None:
89             return self.name
90         else:
91             return self.__fslabel
92     def __set_fslabel(self, val):
93         if val is None:
94             self.__fslabel = None
95         else:
96             self.__fslabel = val[:FSLABEL_MAXLEN]
97     #A string used to label any filesystems created.
98     #
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.
102     #
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)
107
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.
113     #
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().
116     #
117     #Note, this directory does not exist before ImageCreator.mount() is called.
118     #
119     #Note also, this is a read-only attribute.
120     _image = property(__get_image)
121
122     def __get_blocksize(self):
123         return self.__blocksize
124     def __set_blocksize(self, val):
125         if self._instloops:
126             raise CreatorError("_blocksize must be set before calling mount()")
127         try:
128             self.__blocksize = int(val)
129         except ValueError:
130             raise CreatorError("'%s' is not a valid integer value "
131                                "for _blocksize" % val)
132     #The block size used by the image's filesystem.
133     #
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.
136     #
137     #Note, this attribute may only be set before calling mount().
138     _blocksize = property(__get_blocksize, __set_blocksize)
139
140     def __get_fstype(self):
141         return self.__fstype
142     def __set_fstype(self, val):
143         if val != "ext2" and val != "ext3":
144             raise CreatorError("Unknown _fstype '%s' supplied" % val)
145         self.__fstype = val
146     #The type of filesystem used for the image.
147     #
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.
150     #
151     #Note, only ext2 and ext3 are currently supported.
152     #
153     #Note also, this attribute may only be set before calling mount().
154     _fstype = property(__get_fstype, __set_fstype)
155
156     def __get_fsopts(self):
157         return self.__fsopts
158     def __set_fsopts(self, val):
159         self.__fsopts = val
160     #Mount options of filesystem used for the image.
161     #
162     #This can be specified by --fsoptions="xxx,yyy" in part command in
163     #kickstart file.
164     _fsopts = property(__get_fsopts, __set_fsopts)
165
166
167     #
168     # Helpers for subclasses
169     #
170     def _resparse(self, size = None):
171         """Rebuild the filesystem image to be as sparse as possible.
172
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.
176
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.
180
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).
185         """
186         minsize = 0
187         for item in self._instloops:
188             if item['name'] == self._img_name:
189                 minsize = item['loop'].resparse(size)
190             else:
191                 item['loop'].resparse(size)
192
193         return minsize
194
195     def _base_on(self, base_on):
196         if base_on is not None:
197             shutil.copyfile(base_on, self._image)
198
199     def _check_imgdir(self):
200         if self.__imgdir is None:
201             self.__imgdir = self._mkdtemp()
202
203     #
204     # Actual implementation
205     #
206     def _mount_instroot(self, base_on = None):
207         self._check_imgdir()
208         self._base_on(base_on)
209
210         if self.__fstype in ("ext2", "ext3", "ext4"):
211             MyDiskMount = fs.ExtDiskMount
212         elif self.__fstype == "btrfs":
213             MyDiskMount = fs.BtrfsDiskMount
214
215         self._instloops.append({
216                 'name': self._img_name,
217                 'loop': MyDiskMount(fs.SparseLoopbackDisk(self._image, self.__image_size),
218                                     self._instroot,
219                                     self.__fstype,
220                                     self.__blocksize,
221                                     self.fslabel)
222                 })
223
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('/')
231
232             self._instloops.append({
233                 'name': imgname,
234                 'loop': MyDiskMount(fs.SparseLoopbackDisk(os.path.join(self.__imgdir, imgname),
235                                                           self.__image_size),
236                                     os.path.join(self._instroot, point),
237                                     self.__fstype,
238                                     self.__blocksize,
239                                     name)
240                 })
241
242         for item in self._instloops:
243             try:
244                 msger.verbose('Mounting image "%s" on "%s"' %(item['name'], item['loop'].mountdir))
245                 fs.makedirs(item['loop'].mountdir)
246                 item['loop'].mount()
247             except MountError, e:
248                 raise
249
250     def _unmount_instroot(self):
251         for item in reversed(self._instloops):
252             item['loop'].cleanup()
253
254     def _stage_final_image(self):
255         self._resparse()
256         for item in self._instloops:
257             shutil.move(os.path.join(self.__imgdir, item['name']),
258                         os.path.join(self._outdir, item['name']))