Merge lp:~danilo/vmbuilder/bug-536940 into lp:vmbuilder

Proposed by Данило Шеган
Status: Merged
Approved by: Soren Hansen
Approved revision: 455
Merged at revision: 447
Proposed branch: lp:~danilo/vmbuilder/bug-536940
Merge into: lp:vmbuilder
Diff against target: 177 lines (+70/-22)
2 files modified
VMBuilder/contrib/cli.py (+40/-10)
VMBuilder/util.py (+30/-12)
To merge this branch: bzr merge lp:~danilo/vmbuilder/bug-536940
Reviewer Review Type Date Requested Status
Soren Hansen Approve
Review via email: mp+27290@code.launchpad.net

Commit message

(Re-)Add --tmpfs and --tmp options to vmbuilder.

Description of the change

= Bug 536940 =

So, this re-introduces a --tmpfs and --tmp options. I'm not entirely sure of the syntax for --tmpfs, so I implemented what seems to have been the idea according to the documentation and old web site snippets I found. I'd probably prefer it if it was two options ("--use-tmpfs" and "--tmpfs-size"), but I don't have the time to implement that.

I've also simplified util.tmpfile and util.tmpdir functions: they were always called with the same value of 'keep' parameter. I'd be either leaning on removing them altogether, or making them useful for something (like providing a standard vmbuilder prefix like "vmbuilder" :): they are now single-line calls to tempfile module, and we should just use it directly then.

I've done a few more cleanups of my own code as well since I first written it up: I've removed catching of VMBuilderUserError exception because a run_cmd exception will contain much more useful information than my error message in util.set_up_tmpfs and util.clean_up_tmpfs.

There is also one thing I am unsure about: I'm unmounting a tmpfs even when an error occurs, but that means that you can't go into a chroot and figure out what went wrong. I found that more useful (and when I did hit a problem that needed me to go into chroot directly, and wasn't just an error on my part, I simply ran it without --tmpfs option), but I can easily be convinced the opposite.

FWIW, that sys.exit(1) call is removed simply because it's never executed with a raise just before it.

Note: I won't have much time to work on this. For instance, to actually make tmpfile calls safer, we'd at least do the following: actually create a file using tempfile.mkstemp(), and remove it only just prior to doing the operation which is going to write to that file. That wouldn't remove the risks, would just make them much, much smaller. Of course, I understand that vmbuilder is not designed to be run on machines with untrusted users, so even if I had time to spend on it, I don't think it'd be worthwhile.

To post a comment you must log in.
lp:~danilo/vmbuilder/bug-536940 updated
455. By Данило Шеган

Don't try to rm -rf a mounted tmpfs.

Revision history for this message
Soren Hansen (soren) wrote :

On Thu, Jun 10, 2010 at 05:22:23PM -0000, Данило Шеган wrote:
> I've also simplified util.tmpfile and util.tmpdir functions: they were
> always called with the same value of 'keep' parameter.

Ah, good catch. This probably hasn't been the case all along, but I
can't think of an easy way to discover that a particular interface is no
longer used. :(

> I'd be either leaning on removing them altogether, or making them
> useful for something (like providing a standard vmbuilder prefix like
> "vmbuilder" :): they are now single-line calls to tempfile module, and
> we should just use it directly then.

I'm in favour of the vmbuilder prefix idea. It would make cleaning up
after VMBuilder during development easier as well.

> There is also one thing I am unsure about: I'm unmounting a tmpfs even
> when an error occurs, but that means that you can't go into a chroot
> and figure out what went wrong. I found that more useful (and when I
> did hit a problem that needed me to go into chroot directly, and
> wasn't just an error on my part, I simply ran it without --tmpfs
> option), but I can easily be convinced the opposite.

Historically, VMBuilder would clean up very well after itself. This was
slightly annoying during development (as you've identified), and it also
had some unfortunate side effects (it would delete people's /dev (in the
host) in some cases). In 0.12.x a lot of these cleanup procedures are
simply not run at all. What I would like, really, is to be able to pass
a --development option or set an environment variable or something that
would make VMBuilder not clean up after itself at all, but for
end-users, even in case of errors, it would clean up.

> Note: I won't have much time to work on this. For instance, to
> actually make tmpfile calls safer, we'd at least do the following:
> actually create a file using tempfile.mkstemp(), and remove it only
> just prior to doing the operation which is going to write to that
> file. That wouldn't remove the risks, would just make them much, much
> smaller.

Alternatively, we could create a temporary directory with mkdtemp with
mode 0700 and use that as the base directory for all the subsequent
temporary files. That way we avoid the race condition, because there's
simply no way another user could a file in there.

> Of course, I understand that vmbuilder is not designed to be run on
> machines with untrusted users, so even if I had time to spend on it, I
> don't think it'd be worthwhile.

Well, it's not meant to make any assumptions ot the opposite either, so
if someone wants to take a stab at this, that would be great.

The patch looks fine. Thanks!

  review +1
  merge approved

--
Soren Hansen
Ubuntu Developer
http://www.ubuntu.com/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'VMBuilder/contrib/cli.py'
2--- VMBuilder/contrib/cli.py 2010-04-07 19:47:58 +0000
3+++ VMBuilder/contrib/cli.py 2010-06-10 17:56:24 +0000
4@@ -22,6 +22,7 @@
5 import pwd
6 import shutil
7 import sys
8+import tempfile
9 import VMBuilder
10 import VMBuilder.util as util
11 from VMBuilder.disk import parse_size
12@@ -32,6 +33,7 @@
13 arg = 'cli'
14
15 def main(self):
16+ tmpfs_mount_point = None
17 try:
18 optparser = optparse.OptionParser()
19
20@@ -49,6 +51,19 @@
21 group.add_option('--destdir', '-d', type='str', help='Destination directory')
22 group.add_option('--only-chroot', action='store_true', help="Only build the chroot. Don't install it on disk images or anything.")
23 group.add_option('--existing-chroot', help="Use existing chroot.")
24+ group.add_option(
25+ '--tmp', '-t', metavar='DIR', dest='tmp_root',
26+ default=tempfile.gettempdir(),
27+ help=(
28+ 'Use TMP as temporary working space for image generation. '
29+ 'Defaults to $TMPDIR if it is defined or /tmp otherwise. '
30+ '[default: %default]'))
31+ group.add_option(
32+ '--tmpfs', metavar="SIZE",
33+ help=(
34+ 'Use a tmpfs as the working directory, specifying '
35+ 'its size or "-" to use tmpfs default '
36+ '(suid,dev,size=1G).'))
37 optparser.add_option_group(group)
38
39 group = optparse.OptionGroup(optparser, 'Disk')
40@@ -105,7 +120,16 @@
41 distro.set_chroot_dir(self.options.existing_chroot)
42 distro.call_hooks('preflight_check')
43 else:
44- chroot_dir = util.tmpdir()
45+ if self.options.tmpfs is not None:
46+ if str(self.options.tmpfs) == '-':
47+ tmpfs_size = 1024
48+ else:
49+ tmpfs_size = int(self.options.tmpfs)
50+ tmpfs_mount_point = util.set_up_tmpfs(
51+ tmp_root=self.options.tmp_root, size=tmpfs_size)
52+ chroot_dir = tmpfs_mount_point
53+ else:
54+ chroot_dir = util.tmpdir(tmp_root=self.options.tmp_root)
55 distro.set_chroot_dir(chroot_dir)
56 distro.build_chroot()
57
58@@ -123,12 +147,15 @@
59 # and if we reach here, it means the user didn't pass
60 # --only-chroot. Hence, we need to remove it to clean
61 # up after ourselves.
62- if chroot_dir:
63+ if chroot_dir is not None and tmpfs_mount_point is None:
64 util.run_cmd('rm', '-rf', '--one-file-system', chroot_dir)
65 except VMBuilderException, e:
66 logging.error(e)
67 raise
68- sys.exit(1)
69+ finally:
70+ if tmpfs_mount_point is not None:
71+ util.clean_up_tmpfs(tmpfs_mount_point)
72+ util.run_cmd('rmdir', tmpfs_mount_point)
73
74 def fix_ownership(self, filename):
75 """
76@@ -195,19 +222,19 @@
77 swapsize = parse_size(self.options.swapsize)
78 optsize = parse_size(self.options.optsize)
79 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE:
80- tmpfile = util.tmpfile(keep=False)
81+ tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root)
82 hypervisor.add_filesystem(filename=tmpfile, size='%dM' % rootsize, type='ext3', mntpnt='/')
83- tmpfile = util.tmpfile(keep=False)
84+ tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root)
85 hypervisor.add_filesystem(filename=tmpfile, size='%dM' % swapsize, type='swap', mntpnt=None)
86 if optsize > 0:
87- tmpfile = util.tmpfile(keep=False)
88+ tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root)
89 hypervisor.add_filesystem(filename=tmpfile, size='%dM' % optsize, type='ext3', mntpnt='/opt')
90 else:
91 if self.options.raw:
92 disk = hypervisor.add_disk(filename=self.options.raw)
93 else:
94 size = rootsize + swapsize + optsize
95- tmpfile = util.tmpfile(keep=False)
96+ tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root)
97 disk = hypervisor.add_disk(tmpfile, size='%dM' % size)
98 offset = 0
99 disk.add_part(offset, rootsize, default_filesystem, '/')
100@@ -222,7 +249,8 @@
101 try:
102 for line in file(self.options.part):
103 elements = line.strip().split(' ')
104- tmpfile = util.tmpfile(keep=False)
105+ tmpfile = util.tmp_filename(
106+ tmp_root=self.options.tmp_root)
107 if elements[0] == 'root':
108 hypervisor.add_filesystem(elements[1], default_filesystem, filename=tmpfile, mntpnt='/')
109 elif elements[0] == 'swap':
110@@ -256,10 +284,12 @@
111
112 except IOError, (errno, strerror):
113 hypervisor.optparser.error("%s parsing --part option: %s" % (errno, strerror))
114-
115+
116 def do_disk(self, hypervisor, curdisk, size):
117 default_filesystem = hypervisor.distro.preferred_filesystem()
118- disk = hypervisor.add_disk(util.tmpfile(keep=False), size+1)
119+ disk = hypervisor.add_disk(
120+ util.tmp_filename(tmp_root=self.options.tmp_root),
121+ size+1)
122 logging.debug("do_disk - size: %d" % size)
123 offset = 0
124 for pair in curdisk:
125
126=== modified file 'VMBuilder/util.py'
127--- VMBuilder/util.py 2010-02-25 23:41:18 +0000
128+++ VMBuilder/util.py 2010-06-10 17:56:24 +0000
129@@ -168,18 +168,36 @@
130 logging.debug('No such method')
131 return
132
133-def tmpfile(suffix='', keep=True):
134- (fd, filename) = tempfile.mkstemp(suffix=suffix)
135- os.close(fd)
136- if not keep:
137- os.unlink(filename)
138- return filename
139-
140-def tmpdir(suffix='', keep=True):
141- dir = tempfile.mkdtemp(suffix=suffix)
142- if not keep:
143- os.rmdir(dir)
144- return dir
145+def tmp_filename(suffix='', tmp_root=None):
146+ # There is a risk in using tempfile.mktemp(): it's not recommended
147+ # to run vmbuilder on machines with untrusted users.
148+ return tempfile.mktemp(suffix=suffix, dir=tmp_root)
149+
150+def tmpdir(suffix='', tmp_root=None):
151+ return tempfile.mkdtemp(suffix=suffix, dir=tmp_root)
152+
153+def set_up_tmpfs(tmp_root=None, size=1024):
154+ """Sets up a tmpfs storage under `tmp_root` with the size of `size` MB.
155+
156+ `tmp_root` defaults to tempfile.gettempdir().
157+ """
158+ mount_point = tmpdir('tmpfs', tmp_root)
159+ mount_cmd = ["mount", "-t", "tmpfs",
160+ "-o", "size=%dM,mode=0770" % int(size),
161+ "tmpfs", mount_point ]
162+ logging.info('Mounting tmpfs under %s' % mount_point)
163+ logging.debug('Executing: %s' % mount_cmd)
164+ run_cmd(*mount_cmd)
165+
166+ return mount_point
167+
168+def clean_up_tmpfs(mount_point):
169+ """Unmounts a tmpfs storage under `mount_point`."""
170+ umount_cmd = ["umount", "-t", "tmpfs", mount_point ]
171+ logging.info('Unmounting tmpfs from %s' % mount_point)
172+ logging.debug('Executing: %s' % umount_cmd)
173+ run_cmd(*umount_cmd)
174+
175
176 def get_conf_value(context, confparser, key):
177 confvalue = None

Subscribers

People subscribed via source and target branches