Updated chroot's cleanup function
[tools/mic.git] / mic / imager / loop.py
1 #
2 # loop.py : LoopImageCreator classes
3 #
4 # Copyright 2007, Red Hat  Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Library General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 import os
20 import os.path
21 import subprocess
22 import mic.utils.kickstart as kickstart
23 from baseimager import BaseImageCreator
24 from mic.utils.errors import *
25 from mic.utils.fs_related import *
26 from mic.utils.misc import *
27
28 FSLABEL_MAXLEN = 32
29 """The maximum string length supported for LoopImageCreator.fslabel."""
30
31 class LoopImageCreator(BaseImageCreator):
32     """Installs a system into a loopback-mountable filesystem image.
33
34         LoopImageCreator is a straightforward ImageCreator subclass; the system
35         is installed into an ext3 filesystem on a sparse file which can be
36         subsequently loopback-mounted.
37     """
38
39     def __init__(self, creatoropts = None, pkgmgr = None):
40         """Initialize a LoopImageCreator instance.
41
42             This method takes the same arguments as ImageCreator.__init__() with
43             the addition of:
44     
45             fslabel -- A string used as a label for any filesystems created.
46         """
47         BaseImageCreator.__init__(self, creatoropts, pkgmgr)
48
49         self.__fslabel = None
50         self.fslabel = self.name
51
52         self.__minsize_KB = 0
53         self.__blocksize = 4096
54         if self.ks:
55             self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
56             self.__fsopts = kickstart.get_image_fsopts(self.ks, "defaults,noatime")
57         else:
58             self.__fstype = None
59             self.__fsopts = None
60
61         self.__instloop = None
62         self.__imgdir = None
63
64         if self.ks:
65             self.__image_size = kickstart.get_image_size(self.ks,
66                                                          4096L * 1024 * 1024)
67         else:
68             self.__image_size = 0
69
70         self._img_name = self.name + ".img"
71
72     def _set_fstype(self, fstype):
73         self.__fstype = fstype
74
75     def _set_image_size(self, imgsize):
76         self.__image_size = imgsize
77
78     #
79     # Properties
80     #
81     def __get_fslabel(self):
82         if self.__fslabel is None:
83             return self.name
84         else:
85             return self.__fslabel
86     def __set_fslabel(self, val):
87         if val is None:
88             self.__fslabel = None
89         else:
90             self.__fslabel = val[:FSLABEL_MAXLEN]
91     #A string used to label any filesystems created.
92     #
93     #Some filesystems impose a constraint on the maximum allowed size of the
94     #filesystem label. In the case of ext3 it's 16 characters, but in the case
95     #of ISO9660 it's 32 characters.
96     #
97     #mke2fs silently truncates the label, but mkisofs aborts if the label is too
98     #long. So, for convenience sake, any string assigned to this attribute is
99     #silently truncated to FSLABEL_MAXLEN (32) characters.
100     
101     fslabel = property(__get_fslabel, __set_fslabel)
102
103
104     def __get_image(self):
105         if self.__imgdir is None:
106             raise CreatorError("_image is not valid before calling mount()")
107         return self.__imgdir + "/meego.img"
108     #The location of the image file.
109     #
110     #This is the path to the filesystem image. Subclasses may use this path
111     #in order to package the image in _stage_final_image().
112     #    
113     #Note, this directory does not exist before ImageCreator.mount() is called.
114     #
115     #Note also, this is a read-only attribute.
116     _image = property(__get_image)
117
118
119     def __get_blocksize(self):
120         return self.__blocksize
121     def __set_blocksize(self, val):
122         if self.__instloop:
123             raise CreatorError("_blocksize must be set before calling mount()")
124         try:
125             self.__blocksize = int(val)
126         except ValueError:
127             raise CreatorError("'%s' is not a valid integer value "
128                                "for _blocksize" % val)
129     #The block size used by the image's filesystem.
130     #
131     #This is the block size used when creating the filesystem image. Subclasses
132     #may change this if they wish to use something other than a 4k block size.
133     #
134     #Note, this attribute may only be set before calling mount().
135     _blocksize = property(__get_blocksize, __set_blocksize)
136
137
138     def __get_fstype(self):
139         return self.__fstype
140     def __set_fstype(self, val):
141         if val != "ext2" and val != "ext3":
142             raise CreatorError("Unknown _fstype '%s' supplied" % val)
143         self.__fstype = val
144     #The type of filesystem used for the image.
145     #
146     #This is the filesystem type used when creating the filesystem image.
147     #Subclasses may change this if they wish to use something other ext3.
148     #
149     #Note, only ext2 and ext3 are currently supported.
150     #
151     #Note also, this attribute may only be set before calling mount(). 
152     _fstype = property(__get_fstype, __set_fstype)
153
154
155     def __get_fsopts(self):
156         return self.__fsopts
157     def __set_fsopts(self, val):
158         self.__fsopts = val
159     #Mount options of filesystem used for the image.
160     #
161     #This can be specified by --fsoptions="xxx,yyy" in part command in
162     #kickstart file.
163     _fsopts = property(__get_fsopts, __set_fsopts)
164
165
166     #
167     # Helpers for subclasses
168     #
169     def _resparse(self, size = None):
170         """Rebuild the filesystem image to be as sparse as possible.
171
172             This method should be used by subclasses when staging the final image
173             in order to reduce the actual space taken up by the sparse image file
174             to be as little as possible.
175     
176             This is done by resizing the filesystem to the minimal size (thereby
177             eliminating any space taken up by deleted files) and then resizing it
178             back to the supplied size.
179     
180             size -- the size in, in bytes, which the filesystem image should be
181                     resized to after it has been minimized; this defaults to None,
182                     causing the original size specified by the kickstart file to
183                     be used (or 4GiB if not specified in the kickstart).
184         """
185         return self.__instloop.resparse(size)
186
187     def _base_on(self, base_on):
188         shutil.copyfile(base_on, self._image)
189
190     #
191     # Actual implementation
192     #
193     def _mount_instroot(self, base_on = None):
194         if self.__imgdir is None:
195             self.__imgdir = self._mkdtemp()
196
197         if not base_on is None:
198             self._base_on(base_on)
199
200         if self.__fstype in ("ext2", "ext3", "ext4"):
201             MyDiskMount = ExtDiskMount
202         elif self.__fstype == "btrfs":
203             MyDiskMount = BtrfsDiskMount
204
205         self.__instloop = MyDiskMount(SparseLoopbackDisk(self._image, self.__image_size),
206                                        self._instroot,
207                                        self.__fstype,
208                                        self.__blocksize,
209                                        self.fslabel)
210
211         try:
212             self.__instloop.mount()
213         except MountError, e:
214             raise CreatorError("Failed to loopback mount '%s' : %s" %
215                                (self._image, e))
216
217     def _unmount_instroot(self):
218         if not self.__instloop is None:
219             self.__instloop.cleanup()
220
221     def _stage_final_image(self):
222         self._resparse()
223         shutil.move(self._image, self._outdir + "/" + self._img_name)