summaryrefslogtreecommitdiffstats
path: root/scripts/lib/mic/utils/fs_related.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/mic/utils/fs_related.py')
-rw-r--r--scripts/lib/mic/utils/fs_related.py1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/scripts/lib/mic/utils/fs_related.py b/scripts/lib/mic/utils/fs_related.py
new file mode 100644
index 0000000000..b9b9a97175
--- /dev/null
+++ b/scripts/lib/mic/utils/fs_related.py
@@ -0,0 +1,1029 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2007, Red Hat, Inc.
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19from __future__ import with_statement
20import os
21import sys
22import errno
23import stat
24import random
25import string
26import time
27import uuid
28
29from mic import msger
30from mic.utils import runner
31from mic.utils.errors import *
32
33
34def find_binary_inchroot(binary, chroot):
35 paths = ["/usr/sbin",
36 "/usr/bin",
37 "/sbin",
38 "/bin"
39 ]
40
41 for path in paths:
42 bin_path = "%s/%s" % (path, binary)
43 if os.path.exists("%s/%s" % (chroot, bin_path)):
44 return bin_path
45 return None
46
47def find_binary_path(binary):
48 if os.environ.has_key("PATH"):
49 paths = os.environ["PATH"].split(":")
50 else:
51 paths = []
52 if os.environ.has_key("HOME"):
53 paths += [os.environ["HOME"] + "/bin"]
54 paths += ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
55
56 for path in paths:
57 bin_path = "%s/%s" % (path, binary)
58 if os.path.exists(bin_path):
59 return bin_path
60 raise CreatorError("Command '%s' is not available." % binary)
61
62def makedirs(dirname):
63 """A version of os.makedirs() that doesn't throw an
64 exception if the leaf directory already exists.
65 """
66 try:
67 os.makedirs(dirname)
68 except OSError, err:
69 if err.errno != errno.EEXIST:
70 raise
71
72def mksquashfs(in_img, out_img):
73 fullpathmksquashfs = find_binary_path("mksquashfs")
74 args = [fullpathmksquashfs, in_img, out_img]
75
76 if not sys.stdout.isatty():
77 args.append("-no-progress")
78
79 ret = runner.show(args)
80 if ret != 0:
81 raise SquashfsError("'%s' exited with error (%d)" % (' '.join(args), ret))
82
83def resize2fs(fs, size):
84 resize2fs = find_binary_path("resize2fs")
85 if size == 0:
86 # it means to minimalize it
87 return runner.show([resize2fs, '-M', fs])
88 else:
89 return runner.show([resize2fs, fs, "%sK" % (size / 1024,)])
90
91def my_fuser(fp):
92 fuser = find_binary_path("fuser")
93 if not os.path.exists(fp):
94 return False
95
96 rc = runner.quiet([fuser, "-s", fp])
97 if rc == 0:
98 for pid in runner.outs([fuser, fp]).split():
99 fd = open("/proc/%s/cmdline" % pid, "r")
100 cmdline = fd.read()
101 fd.close()
102 if cmdline[:-1] == "/bin/bash":
103 return True
104
105 # not found
106 return False
107
108class BindChrootMount:
109 """Represents a bind mount of a directory into a chroot."""
110 def __init__(self, src, chroot, dest = None, option = None):
111 self.root = os.path.abspath(os.path.expanduser(chroot))
112 self.option = option
113
114 self.orig_src = self.src = src
115 if os.path.islink(src):
116 self.src = os.readlink(src)
117 if not self.src.startswith('/'):
118 self.src = os.path.abspath(os.path.join(os.path.dirname(src),
119 self.src))
120
121 if not dest:
122 dest = self.src
123 self.dest = os.path.join(self.root, dest.lstrip('/'))
124
125 self.mounted = False
126 self.mountcmd = find_binary_path("mount")
127 self.umountcmd = find_binary_path("umount")
128
129 def ismounted(self):
130 with open('/proc/mounts') as f:
131 for line in f:
132 if line.split()[1] == os.path.abspath(self.dest):
133 return True
134
135 return False
136
137 def has_chroot_instance(self):
138 lock = os.path.join(self.root, ".chroot.lock")
139 return my_fuser(lock)
140
141 def mount(self):
142 if self.mounted or self.ismounted():
143 return
144
145 makedirs(self.dest)
146 rc = runner.show([self.mountcmd, "--bind", self.src, self.dest])
147 if rc != 0:
148 raise MountError("Bind-mounting '%s' to '%s' failed" %
149 (self.src, self.dest))
150 if self.option:
151 rc = runner.show([self.mountcmd, "--bind", "-o", "remount,%s" % self.option, self.dest])
152 if rc != 0:
153 raise MountError("Bind-remounting '%s' failed" % self.dest)
154
155 self.mounted = True
156 if os.path.islink(self.orig_src):
157 dest = os.path.join(self.root, self.orig_src.lstrip('/'))
158 if not os.path.exists(dest):
159 os.symlink(self.src, dest)
160
161 def unmount(self):
162 if self.has_chroot_instance():
163 return
164
165 if self.ismounted():
166 runner.show([self.umountcmd, "-l", self.dest])
167 self.mounted = False
168
169class LoopbackMount:
170 """LoopbackMount compatibility layer for old API"""
171 def __init__(self, lofile, mountdir, fstype = None):
172 self.diskmount = DiskMount(LoopbackDisk(lofile,size = 0),mountdir,fstype,rmmountdir = True)
173 self.losetup = False
174 self.losetupcmd = find_binary_path("losetup")
175
176 def cleanup(self):
177 self.diskmount.cleanup()
178
179 def unmount(self):
180 self.diskmount.unmount()
181
182 def lounsetup(self):
183 if self.losetup:
184 runner.show([self.losetupcmd, "-d", self.loopdev])
185 self.losetup = False
186 self.loopdev = None
187
188 def loopsetup(self):
189 if self.losetup:
190 return
191
192 self.loopdev = get_loop_device(self.losetupcmd, self.lofile)
193 self.losetup = True
194
195 def mount(self):
196 self.diskmount.mount()
197
198class SparseLoopbackMount(LoopbackMount):
199 """SparseLoopbackMount compatibility layer for old API"""
200 def __init__(self, lofile, mountdir, size, fstype = None):
201 self.diskmount = DiskMount(SparseLoopbackDisk(lofile,size),mountdir,fstype,rmmountdir = True)
202
203 def expand(self, create = False, size = None):
204 self.diskmount.disk.expand(create, size)
205
206 def truncate(self, size = None):
207 self.diskmount.disk.truncate(size)
208
209 def create(self):
210 self.diskmount.disk.create()
211
212class SparseExtLoopbackMount(SparseLoopbackMount):
213 """SparseExtLoopbackMount compatibility layer for old API"""
214 def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
215 self.diskmount = ExtDiskMount(SparseLoopbackDisk(lofile,size), mountdir, fstype, blocksize, fslabel, rmmountdir = True)
216
217
218 def __format_filesystem(self):
219 self.diskmount.__format_filesystem()
220
221 def create(self):
222 self.diskmount.disk.create()
223
224 def resize(self, size = None):
225 return self.diskmount.__resize_filesystem(size)
226
227 def mount(self):
228 self.diskmount.mount()
229
230 def __fsck(self):
231 self.extdiskmount.__fsck()
232
233 def __get_size_from_filesystem(self):
234 return self.diskmount.__get_size_from_filesystem()
235
236 def __resize_to_minimal(self):
237 return self.diskmount.__resize_to_minimal()
238
239 def resparse(self, size = None):
240 return self.diskmount.resparse(size)
241
242class Disk:
243 """Generic base object for a disk
244
245 The 'create' method must make the disk visible as a block device - eg
246 by calling losetup. For RawDisk, this is obviously a no-op. The 'cleanup'
247 method must undo the 'create' operation.
248 """
249 def __init__(self, size, device = None):
250 self._device = device
251 self._size = size
252
253 def create(self):
254 pass
255
256 def cleanup(self):
257 pass
258
259 def get_device(self):
260 return self._device
261 def set_device(self, path):
262 self._device = path
263 device = property(get_device, set_device)
264
265 def get_size(self):
266 return self._size
267 size = property(get_size)
268
269
270class RawDisk(Disk):
271 """A Disk backed by a block device.
272 Note that create() is a no-op.
273 """
274 def __init__(self, size, device):
275 Disk.__init__(self, size, device)
276
277 def fixed(self):
278 return True
279
280 def exists(self):
281 return True
282
283class LoopbackDisk(Disk):
284 """A Disk backed by a file via the loop module."""
285 def __init__(self, lofile, size):
286 Disk.__init__(self, size)
287 self.lofile = lofile
288 self.losetupcmd = find_binary_path("losetup")
289
290 def fixed(self):
291 return False
292
293 def exists(self):
294 return os.path.exists(self.lofile)
295
296 def create(self):
297 if self.device is not None:
298 return
299
300 self.device = get_loop_device(self.losetupcmd, self.lofile)
301
302 def cleanup(self):
303 if self.device is None:
304 return
305 msger.debug("Losetup remove %s" % self.device)
306 rc = runner.show([self.losetupcmd, "-d", self.device])
307 self.device = None
308
309class SparseLoopbackDisk(LoopbackDisk):
310 """A Disk backed by a sparse file via the loop module."""
311 def __init__(self, lofile, size):
312 LoopbackDisk.__init__(self, lofile, size)
313
314 def expand(self, create = False, size = None):
315 flags = os.O_WRONLY
316 if create:
317 flags |= os.O_CREAT
318 if not os.path.exists(self.lofile):
319 makedirs(os.path.dirname(self.lofile))
320
321 if size is None:
322 size = self.size
323
324 msger.debug("Extending sparse file %s to %d" % (self.lofile, size))
325 if create:
326 fd = os.open(self.lofile, flags, 0644)
327 else:
328 fd = os.open(self.lofile, flags)
329
330 if size <= 0:
331 size = 1
332 try:
333 os.ftruncate(fd, size)
334 except:
335 # may be limited by 2G in 32bit env
336 os.ftruncate(fd, 2**31L)
337
338 os.close(fd)
339
340 def truncate(self, size = None):
341 if size is None:
342 size = self.size
343
344 msger.debug("Truncating sparse file %s to %d" % (self.lofile, size))
345 fd = os.open(self.lofile, os.O_WRONLY)
346 os.ftruncate(fd, size)
347 os.close(fd)
348
349 def create(self):
350 self.expand(create = True)
351 LoopbackDisk.create(self)
352
353class Mount:
354 """A generic base class to deal with mounting things."""
355 def __init__(self, mountdir):
356 self.mountdir = mountdir
357
358 def cleanup(self):
359 self.unmount()
360
361 def mount(self, options = None):
362 pass
363
364 def unmount(self):
365 pass
366
367class DiskMount(Mount):
368 """A Mount object that handles mounting of a Disk."""
369 def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
370 Mount.__init__(self, mountdir)
371
372 self.disk = disk
373 self.fstype = fstype
374 self.rmmountdir = rmmountdir
375
376 self.mounted = False
377 self.rmdir = False
378 if fstype:
379 self.mkfscmd = find_binary_path("mkfs." + self.fstype)
380 else:
381 self.mkfscmd = None
382 self.mountcmd = find_binary_path("mount")
383 self.umountcmd = find_binary_path("umount")
384
385 def cleanup(self):
386 Mount.cleanup(self)
387 self.disk.cleanup()
388
389 def unmount(self):
390 if self.mounted:
391 msger.debug("Unmounting directory %s" % self.mountdir)
392 runner.quiet('sync') # sync the data on this mount point
393 rc = runner.show([self.umountcmd, "-l", self.mountdir])
394 if rc == 0:
395 self.mounted = False
396 else:
397 raise MountError("Failed to umount %s" % self.mountdir)
398 if self.rmdir and not self.mounted:
399 try:
400 os.rmdir(self.mountdir)
401 except OSError, e:
402 pass
403 self.rmdir = False
404
405
406 def __create(self):
407 self.disk.create()
408
409
410 def mount(self, options = None):
411 if self.mounted:
412 return
413
414 if not os.path.isdir(self.mountdir):
415 msger.debug("Creating mount point %s" % self.mountdir)
416 os.makedirs(self.mountdir)
417 self.rmdir = self.rmmountdir
418
419 self.__create()
420
421 msger.debug("Mounting %s at %s" % (self.disk.device, self.mountdir))
422 if options:
423 args = [ self.mountcmd, "-o", options, self.disk.device, self.mountdir ]
424 else:
425 args = [ self.mountcmd, self.disk.device, self.mountdir ]
426 if self.fstype:
427 args.extend(["-t", self.fstype])
428
429 rc = runner.show(args)
430 if rc != 0:
431 raise MountError("Failed to mount '%s' to '%s' with command '%s'. Retval: %s" %
432 (self.disk.device, self.mountdir, " ".join(args), rc))
433
434 self.mounted = True
435
436class ExtDiskMount(DiskMount):
437 """A DiskMount object that is able to format/resize ext[23] filesystems."""
438 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
439 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
440 self.blocksize = blocksize
441 self.fslabel = fslabel.replace("/", "")
442 self.uuid = str(uuid.uuid4())
443 self.skipformat = skipformat
444 self.fsopts = fsopts
445 self.extopts = None
446 self.dumpe2fs = find_binary_path("dumpe2fs")
447 self.tune2fs = find_binary_path("tune2fs")
448
449 def __parse_field(self, output, field):
450 for line in output.split("\n"):
451 if line.startswith(field + ":"):
452 return line[len(field) + 1:].strip()
453
454 raise KeyError("Failed to find field '%s' in output" % field)
455
456 def __format_filesystem(self):
457 if self.skipformat:
458 msger.debug("Skip filesystem format.")
459 return
460
461 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
462 cmdlist = [self.mkfscmd, "-F", "-L", self.fslabel, "-m", "1", "-b",
463 str(self.blocksize), "-U", self.uuid]
464 if self.extopts:
465 cmdlist.extend(self.extopts.split())
466 cmdlist.extend([self.disk.device])
467
468 rc, errout = runner.runtool(cmdlist, catch=2)
469 if rc != 0:
470 raise MountError("Error creating %s filesystem on disk %s:\n%s" %
471 (self.fstype, self.disk.device, errout))
472
473 if not self.extopts:
474 msger.debug("Tuning filesystem on %s" % self.disk.device)
475 runner.show([self.tune2fs, "-c0", "-i0", "-Odir_index", "-ouser_xattr,acl", self.disk.device])
476
477 def __resize_filesystem(self, size = None):
478 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
479
480 if size is None:
481 size = self.disk.size
482
483 if size == current_size:
484 return
485
486 if size > current_size:
487 self.disk.expand(size)
488
489 self.__fsck()
490
491 resize2fs(self.disk.lofile, size)
492 return size
493
494 def __create(self):
495 resize = False
496 if not self.disk.fixed() and self.disk.exists():
497 resize = True
498
499 self.disk.create()
500
501 if resize:
502 self.__resize_filesystem()
503 else:
504 self.__format_filesystem()
505
506 def mount(self, options = None):
507 self.__create()
508 DiskMount.mount(self, options)
509
510 def __fsck(self):
511 msger.info("Checking filesystem %s" % self.disk.lofile)
512 runner.quiet(["/sbin/e2fsck", "-f", "-y", self.disk.lofile])
513
514 def __get_size_from_filesystem(self):
515 return int(self.__parse_field(runner.outs([self.dumpe2fs, '-h', self.disk.lofile]),
516 "Block count")) * self.blocksize
517
518 def __resize_to_minimal(self):
519 self.__fsck()
520
521 #
522 # Use a binary search to find the minimal size
523 # we can resize the image to
524 #
525 bot = 0
526 top = self.__get_size_from_filesystem()
527 while top != (bot + 1):
528 t = bot + ((top - bot) / 2)
529
530 if not resize2fs(self.disk.lofile, t):
531 top = t
532 else:
533 bot = t
534 return top
535
536 def resparse(self, size = None):
537 self.cleanup()
538 if size == 0:
539 minsize = 0
540 else:
541 minsize = self.__resize_to_minimal()
542 self.disk.truncate(minsize)
543
544 self.__resize_filesystem(size)
545 return minsize
546
547class VfatDiskMount(DiskMount):
548 """A DiskMount object that is able to format vfat/msdos filesystems."""
549 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
550 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
551 self.blocksize = blocksize
552 self.fslabel = fslabel.replace("/", "")
553 rand1 = random.randint(0, 2**16 - 1)
554 rand2 = random.randint(0, 2**16 - 1)
555 self.uuid = "%04X-%04X" % (rand1, rand2)
556 self.skipformat = skipformat
557 self.fsopts = fsopts
558 self.fsckcmd = find_binary_path("fsck." + self.fstype)
559
560 def __format_filesystem(self):
561 if self.skipformat:
562 msger.debug("Skip filesystem format.")
563 return
564
565 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
566 rc = runner.show([self.mkfscmd, "-n", self.fslabel,
567 "-i", self.uuid.replace("-", ""), self.disk.device])
568 if rc != 0:
569 raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
570
571 msger.verbose("Tuning filesystem on %s" % self.disk.device)
572
573 def __resize_filesystem(self, size = None):
574 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
575
576 if size is None:
577 size = self.disk.size
578
579 if size == current_size:
580 return
581
582 if size > current_size:
583 self.disk.expand(size)
584
585 self.__fsck()
586
587 #resize2fs(self.disk.lofile, size)
588 return size
589
590 def __create(self):
591 resize = False
592 if not self.disk.fixed() and self.disk.exists():
593 resize = True
594
595 self.disk.create()
596
597 if resize:
598 self.__resize_filesystem()
599 else:
600 self.__format_filesystem()
601
602 def mount(self, options = None):
603 self.__create()
604 DiskMount.mount(self, options)
605
606 def __fsck(self):
607 msger.debug("Checking filesystem %s" % self.disk.lofile)
608 runner.show([self.fsckcmd, "-y", self.disk.lofile])
609
610 def __get_size_from_filesystem(self):
611 return self.disk.size
612
613 def __resize_to_minimal(self):
614 self.__fsck()
615
616 #
617 # Use a binary search to find the minimal size
618 # we can resize the image to
619 #
620 bot = 0
621 top = self.__get_size_from_filesystem()
622 return top
623
624 def resparse(self, size = None):
625 self.cleanup()
626 minsize = self.__resize_to_minimal()
627 self.disk.truncate(minsize)
628 self.__resize_filesystem(size)
629 return minsize
630
631class BtrfsDiskMount(DiskMount):
632 """A DiskMount object that is able to format/resize btrfs filesystems."""
633 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
634 self.__check_btrfs()
635 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
636 self.blocksize = blocksize
637 self.fslabel = fslabel.replace("/", "")
638 self.uuid = None
639 self.skipformat = skipformat
640 self.fsopts = fsopts
641 self.blkidcmd = find_binary_path("blkid")
642 self.btrfsckcmd = find_binary_path("btrfsck")
643
644 def __check_btrfs(self):
645 found = False
646 """ Need to load btrfs module to mount it """
647 load_module("btrfs")
648 for line in open("/proc/filesystems").xreadlines():
649 if line.find("btrfs") > -1:
650 found = True
651 break
652 if not found:
653 raise MountError("Your system can't mount btrfs filesystem, please make sure your kernel has btrfs support and the module btrfs.ko has been loaded.")
654
655 # disable selinux, selinux will block write
656 if os.path.exists("/usr/sbin/setenforce"):
657 runner.show(["/usr/sbin/setenforce", "0"])
658
659 def __parse_field(self, output, field):
660 for line in output.split(" "):
661 if line.startswith(field + "="):
662 return line[len(field) + 1:].strip().replace("\"", "")
663
664 raise KeyError("Failed to find field '%s' in output" % field)
665
666 def __format_filesystem(self):
667 if self.skipformat:
668 msger.debug("Skip filesystem format.")
669 return
670
671 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
672 rc = runner.show([self.mkfscmd, "-L", self.fslabel, self.disk.device])
673 if rc != 0:
674 raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
675
676 self.uuid = self.__parse_field(runner.outs([self.blkidcmd, self.disk.device]), "UUID")
677
678 def __resize_filesystem(self, size = None):
679 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
680
681 if size is None:
682 size = self.disk.size
683
684 if size == current_size:
685 return
686
687 if size > current_size:
688 self.disk.expand(size)
689
690 self.__fsck()
691 return size
692
693 def __create(self):
694 resize = False
695 if not self.disk.fixed() and self.disk.exists():
696 resize = True
697
698 self.disk.create()
699
700 if resize:
701 self.__resize_filesystem()
702 else:
703 self.__format_filesystem()
704
705 def mount(self, options = None):
706 self.__create()
707 DiskMount.mount(self, options)
708
709 def __fsck(self):
710 msger.debug("Checking filesystem %s" % self.disk.lofile)
711 runner.quiet([self.btrfsckcmd, self.disk.lofile])
712
713 def __get_size_from_filesystem(self):
714 return self.disk.size
715
716 def __resize_to_minimal(self):
717 self.__fsck()
718
719 return self.__get_size_from_filesystem()
720
721 def resparse(self, size = None):
722 self.cleanup()
723 minsize = self.__resize_to_minimal()
724 self.disk.truncate(minsize)
725 self.__resize_filesystem(size)
726 return minsize
727
728class DeviceMapperSnapshot(object):
729 def __init__(self, imgloop, cowloop):
730 self.imgloop = imgloop
731 self.cowloop = cowloop
732
733 self.__created = False
734 self.__name = None
735 self.dmsetupcmd = find_binary_path("dmsetup")
736
737 """Load dm_snapshot if it isn't loaded"""
738 load_module("dm_snapshot")
739
740 def get_path(self):
741 if self.__name is None:
742 return None
743 return os.path.join("/dev/mapper", self.__name)
744 path = property(get_path)
745
746 def create(self):
747 if self.__created:
748 return
749
750 self.imgloop.create()
751 self.cowloop.create()
752
753 self.__name = "imgcreate-%d-%d" % (os.getpid(),
754 random.randint(0, 2**16))
755
756 size = os.stat(self.imgloop.lofile)[stat.ST_SIZE]
757
758 table = "0 %d snapshot %s %s p 8" % (size / 512,
759 self.imgloop.device,
760 self.cowloop.device)
761
762 args = [self.dmsetupcmd, "create", self.__name, "--table", table]
763 if runner.show(args) != 0:
764 self.cowloop.cleanup()
765 self.imgloop.cleanup()
766 raise SnapshotError("Could not create snapshot device using: " + ' '.join(args))
767
768 self.__created = True
769
770 def remove(self, ignore_errors = False):
771 if not self.__created:
772 return
773
774 time.sleep(2)
775 rc = runner.show([self.dmsetupcmd, "remove", self.__name])
776 if not ignore_errors and rc != 0:
777 raise SnapshotError("Could not remove snapshot device")
778
779 self.__name = None
780 self.__created = False
781
782 self.cowloop.cleanup()
783 self.imgloop.cleanup()
784
785 def get_cow_used(self):
786 if not self.__created:
787 return 0
788
789 #
790 # dmsetup status on a snapshot returns e.g.
791 # "0 8388608 snapshot 416/1048576"
792 # or, more generally:
793 # "A B snapshot C/D"
794 # where C is the number of 512 byte sectors in use
795 #
796 out = runner.outs([self.dmsetupcmd, "status", self.__name])
797 try:
798 return int((out.split()[3]).split('/')[0]) * 512
799 except ValueError:
800 raise SnapshotError("Failed to parse dmsetup status: " + out)
801
802def create_image_minimizer(path, image, minimal_size):
803 """
804 Builds a copy-on-write image which can be used to
805 create a device-mapper snapshot of an image where
806 the image's filesystem is as small as possible
807
808 The steps taken are:
809 1) Create a sparse COW
810 2) Loopback mount the image and the COW
811 3) Create a device-mapper snapshot of the image
812 using the COW
813 4) Resize the filesystem to the minimal size
814 5) Determine the amount of space used in the COW
815 6) Restroy the device-mapper snapshot
816 7) Truncate the COW, removing unused space
817 8) Create a squashfs of the COW
818 """
819 imgloop = LoopbackDisk(image, None) # Passing bogus size - doesn't matter
820
821 cowloop = SparseLoopbackDisk(os.path.join(os.path.dirname(path), "osmin"),
822 64L * 1024L * 1024L)
823
824 snapshot = DeviceMapperSnapshot(imgloop, cowloop)
825
826 try:
827 snapshot.create()
828
829 resize2fs(snapshot.path, minimal_size)
830
831 cow_used = snapshot.get_cow_used()
832 finally:
833 snapshot.remove(ignore_errors = (not sys.exc_info()[0] is None))
834
835 cowloop.truncate(cow_used)
836
837 mksquashfs(cowloop.lofile, path)
838
839 os.unlink(cowloop.lofile)
840
841def load_module(module):
842 found = False
843 for line in open('/proc/modules').xreadlines():
844 if line.startswith("%s " % module):
845 found = True
846 break
847 if not found:
848 msger.info("Loading %s..." % module)
849 runner.quiet(['modprobe', module])
850
851class LoopDevice(object):
852 def __init__(self, loopid=None):
853 self.device = None
854 self.loopid = loopid
855 self.created = False
856 self.kpartxcmd = find_binary_path("kpartx")
857 self.losetupcmd = find_binary_path("losetup")
858
859 def register(self, device):
860 self.device = device
861 self.loopid = None
862 self.created = True
863
864 def reg_atexit(self):
865 import atexit
866 atexit.register(self.close)
867
868 def _genloopid(self):
869 import glob
870 if not glob.glob("/dev/loop[0-9]*"):
871 return 10
872
873 fint = lambda x: x[9:].isdigit() and int(x[9:]) or 0
874 maxid = 1 + max(filter(lambda x: x<100,
875 map(fint, glob.glob("/dev/loop[0-9]*"))))
876 if maxid < 10: maxid = 10
877 if maxid >= 100: raise
878 return maxid
879
880 def _kpseek(self, device):
881 rc, out = runner.runtool([self.kpartxcmd, '-l', '-v', device])
882 if rc != 0:
883 raise MountError("Can't query dm snapshot on %s" % device)
884 for line in out.splitlines():
885 if line and line.startswith("loop"):
886 return True
887 return False
888
889 def _loseek(self, device):
890 import re
891 rc, out = runner.runtool([self.losetupcmd, '-a'])
892 if rc != 0:
893 raise MountError("Failed to run 'losetup -a'")
894 for line in out.splitlines():
895 m = re.match("([^:]+): .*", line)
896 if m and m.group(1) == device:
897 return True
898 return False
899
900 def create(self):
901 if not self.created:
902 if not self.loopid:
903 self.loopid = self._genloopid()
904 self.device = "/dev/loop%d" % self.loopid
905 if os.path.exists(self.device):
906 if self._loseek(self.device):
907 raise MountError("Device busy: %s" % self.device)
908 else:
909 self.created = True
910 return
911
912 mknod = find_binary_path('mknod')
913 rc = runner.show([mknod, '-m664', self.device, 'b', '7', str(self.loopid)])
914 if rc != 0:
915 raise MountError("Failed to create device %s" % self.device)
916 else:
917 self.created = True
918
919 def close(self):
920 if self.created:
921 try:
922 self.cleanup()
923 self.device = None
924 except MountError, e:
925 msger.error("%s" % e)
926
927 def cleanup(self):
928
929 if self.device is None:
930 return
931
932
933 if self._kpseek(self.device):
934 if self.created:
935 for i in range(3, os.sysconf("SC_OPEN_MAX")):
936 try:
937 os.close(i)
938 except:
939 pass
940 runner.quiet([self.kpartxcmd, "-d", self.device])
941 if self._loseek(self.device):
942 runner.quiet([self.losetupcmd, "-d", self.device])
943 # FIXME: should sleep a while between two loseek
944 if self._loseek(self.device):
945 msger.warning("Can't cleanup loop device %s" % self.device)
946 elif self.loopid:
947 os.unlink(self.device)
948
949DEVICE_PIDFILE_DIR = "/var/tmp/mic/device"
950DEVICE_LOCKFILE = "/var/lock/__mic_loopdev.lock"
951
952def get_loop_device(losetupcmd, lofile):
953 global DEVICE_PIDFILE_DIR
954 global DEVICE_LOCKFILE
955
956 import fcntl
957 makedirs(os.path.dirname(DEVICE_LOCKFILE))
958 fp = open(DEVICE_LOCKFILE, 'w')
959 fcntl.flock(fp, fcntl.LOCK_EX)
960 try:
961 loopdev = None
962 devinst = LoopDevice()
963
964 # clean up left loop device first
965 clean_loop_devices()
966
967 # provide an avaible loop device
968 rc, out = runner.runtool([losetupcmd, "--find"])
969 if rc == 0:
970 loopdev = out.split()[0]
971 devinst.register(loopdev)
972 if not loopdev or not os.path.exists(loopdev):
973 devinst.create()
974 loopdev = devinst.device
975
976 # setup a loop device for image file
977 rc = runner.show([losetupcmd, loopdev, lofile])
978 if rc != 0:
979 raise MountError("Failed to setup loop device for '%s'" % lofile)
980
981 devinst.reg_atexit()
982
983 # try to save device and pid
984 makedirs(DEVICE_PIDFILE_DIR)
985 pidfile = os.path.join(DEVICE_PIDFILE_DIR, os.path.basename(loopdev))
986 if os.path.exists(pidfile):
987 os.unlink(pidfile)
988 with open(pidfile, 'w') as wf:
989 wf.write(str(os.getpid()))
990
991 except MountError, err:
992 raise CreatorError("%s" % str(err))
993 except:
994 raise
995 finally:
996 try:
997 fcntl.flock(fp, fcntl.LOCK_UN)
998 fp.close()
999 os.unlink(DEVICE_LOCKFILE)
1000 except:
1001 pass
1002
1003 return loopdev
1004
1005def clean_loop_devices(piddir=DEVICE_PIDFILE_DIR):
1006 if not os.path.exists(piddir) or not os.path.isdir(piddir):
1007 return
1008
1009 for loopdev in os.listdir(piddir):
1010 pidfile = os.path.join(piddir, loopdev)
1011 try:
1012 with open(pidfile, 'r') as rf:
1013 devpid = int(rf.read())
1014 except:
1015 devpid = None
1016
1017 # if the process using this device is alive, skip it
1018 if not devpid or os.path.exists(os.path.join('/proc', str(devpid))):
1019 continue
1020
1021 # try to clean it up
1022 try:
1023 devinst = LoopDevice()
1024 devinst.register(os.path.join('/dev', loopdev))
1025 devinst.cleanup()
1026 os.unlink(pidfile)
1027 except:
1028 pass
1029