/[gli]/branches/overhaul/src/Partitioning.py
Gentoo

Contents of /branches/overhaul/src/Partitioning.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1543 - (show annotations) (download) (as text)
Sat Oct 28 21:28:28 2006 UTC (8 years, 1 month ago) by agaffney
File MIME type: text/x-python
File size: 20856 byte(s)
more reorg and cleanup
1 # Copyright 1999-2006 Gentoo Foundation
2 # This source code is distributed under the terms of version 2 of the GNU
3 # General Public License as published by the Free Software Foundation, a copy
4 # of which can be found in the main directory of this project.
5
6 import commands, string, os, parted
7 from glob import glob
8 from GLIException import *
9 import GLIUtility
10 import time
11
12 MEGABYTE = 1024 * 1024
13
14 # these are here so that we can change them easily in future
15 # the values were chosen to represent perfect floating point representations
16 FREE_MINOR_FRAC_PRI = 1.0/32.0
17 FREE_MINOR_FRAC_LOG = 1.0/8.0
18
19 supported_filesystems = {
20 'all': ( 'ext2', 'ext3', 'linux-swap', 'xfs', 'jfs', 'reiserfs' ),
21 'x86': ( 'ntfs', 'fat16', 'fat32' ),
22 'amd64': ( 'ntfs', 'fat16', 'fat32' ),
23 'ppc': ( 'reiserfs', 'apple_bootstrap', 'hfs' )
24 }
25
26 labelinfo = {
27 'msdos': { 'ignoredparts': [], 'extended': True },
28 'mac': { 'ignoredparts': [1], 'extended': False },
29 'sun': { 'ignoredparts': [3], 'extended': False },
30 'loop': { 'ignoredparts': [], 'extended': False }
31 }
32
33 archinfo = {
34 'sparc': 'sun',
35 'hppa': 'msdos',
36 'x86': 'msdos',
37 'amd64': 'msdos',
38 'ppc': 'mac'
39 }
40
41 ##
42 # This class provides a partitioning abstraction for the frontends
43 class Device:
44 "Class representing a partitionable device."
45
46 _device = None
47 _partitions = None
48 _geometry = None
49 _parted_dev = None
50 _parted_disk = None
51 _arch = None
52 _disklabel = None
53
54 ##
55 # Initialization function for Device class
56 # @param device Device node (e.g. /dev/hda) of device being represented
57 # @param arch Architecture that we're partition for (defaults to 'x86' for now)
58 def __init__(self, device, arch="x86"):
59 self._device = device
60 self._arch = arch
61 self._partitions = []
62 self._geometry = {}
63 self._parted_dev = parted.PedDevice.get(self._device)
64 try:
65 self._parted_disk = parted.PedDisk.new(self._parted_dev)
66 except:
67 self._parted_disk = self._parted_dev.disk_new_fresh(parted.disk_type_get(archinfo[self._arch]))
68 self._disklabel = self._parted_disk.type.name
69 self._labelinfo = labelinfo[self._disklabel]
70 self.set_disk_geometry_from_disk()
71 self.set_partitions_from_disk()
72
73 def __getitem__(self, name):
74 return self.get_partition(name)
75
76 def __iter__(self):
77 for part in self.get_ordered_partition_list():
78 yield part
79
80 ##
81 # Returns list of supported filesystems based on arch
82 def get_supported_filesystems(self):
83 return supported_filesystems['all'] + supported_filesystems[self._arch]
84
85 ##
86 # Sets disk geometry info from disk. This function is used internally by __init__()
87 def set_disk_geometry_from_disk(self):
88 self._geometry = {
89 'sector_size': self._parted_dev.sector_size,
90 'total_bytes': self._parted_dev.length * self._parted_dev.sector_size,
91 'heads': self._parted_dev.heads,
92 'cylinders': self._parted_dev.cylinders,
93 'sectors': self._parted_dev.sectors,
94 'cylinder_bytes': self._geometry['heads'] * self._geometry['sectors'] * self._parted_dev.sector_size,
95 'total_sectors': self._parted_dev.length,
96 'sectors_in_cylinder': self._parted_dev.heads * self._parted_dev.sectors,
97 'total_mb': long((self._parted_dev.length * self._parted_dev.sector_size) / MEGABYTE)
98 }
99
100 ##
101 # Sets partition info from disk.
102 def set_partitions_from_disk(self):
103 self._partitions = []
104 parted_part = self._parted_disk.next_partition()
105 while parted_part:
106 # Make sure it's a physical partition or unallocated space
107 if parted_part.num >= 1 or parted_part.type_name == "free":
108 self._partitions.append(Partition(self, parted_part))
109 parted_part = self._parted_disk.next_partition(parted_part)
110
111 ##
112 # Returns the partition object with the specified minor
113 # @param minor Minor number of partition object to get
114 def get_partition(self, minor):
115 for part in self._partitions:
116 if part.get_minor() == minor:
117 return part
118 return None
119
120 def get_partition_position(self, minor):
121 for i, part in enumerate(self._partitions):
122 if part.get_minor() == minor:
123 return i
124 return -1
125
126 ##
127 # Returns name of device (e.g. /dev/hda) being represented
128 def get_device(self):
129 return self._device
130
131 ##
132 # Uses magic to apply the recommended partition layout
133 def do_recommended(self):
134 freeidx = -1
135 free_part = None
136 recommended_parts = [ { 'type': "ext2", 'size': 100, 'mountpoint': "/boot" },
137 { 'type': "linux-swap", 'size': 512, 'mountpoint': "" },
138 { 'type': "ext3", 'size': "*", 'mountpoint': "/" } ]
139 to_create = []
140 physical_memory = int(GLIUtility.spawn(r"free -m | egrep '^Mem:' | sed -e 's/^Mem: \+//' -e 's/ \+.\+$//'", return_output=True)[1].strip())
141 # parts = self.get_ordered_partition_list()
142 # Search for concurrent unallocated space >=4GB
143 for idx, part in self._partitions:
144 if part['type'] == "free" and part['mb'] >= 4096:
145 freeidx = idx
146 free_part = part
147 break
148 else:
149 raise GLIException("RecommendedPartitionLayoutError", "notice", "do_recommended", "You do not have atleast 4GB of concurrent unallocated space. Please remove some partitions and try again.")
150 remaining_free = free_part['mb']
151 # XXX: I left off here
152 for newpart in recommended_parts:
153 # extended/logical partitions suck like a hoover
154 if self._labelinfo['extended'] and free_minor == (3 + FREE_MINOR_FRAC_PRI) and not newpart == recommended_parts[-1]:
155 if self.get_extended_partition():
156 raise GLIException("RecommendedPartitionLayoutError", "notice", "do_recommended", "This code is not yet robust enough to handle automatic partitioning with your current layout.")
157 to_create.append({ 'type': "extended", 'size': remaining_free, 'mountpoint': "", 'free_minor': free_minor })
158 free_minor = 4 + FREE_MINOR_FRAC_LOG
159 newpart['free_minor'] = free_minor
160 # Small hack to calculate optimal swap partition size
161 if newpart['type'] == "linux-swap" and physical_memory:
162 newpart['size'] = physical_memory * 2
163 if newpart['size'] > 1024:
164 newpart['size'] = 1024
165 to_create.append(newpart)
166 free_minor = free_minor + 1
167 if not newpart['size'] == "*":
168 remaining_free = remaining_free - newpart['size']
169 for newpart in to_create:
170 if newpart['size'] == "*":
171 # This doesn't seem quite right...it should probably be set to remaining_free
172 # newpart['size'] = self.get_partition(newpart['free_minor']).get_mb()
173 newpart['size'] = remaining_free
174 self.add_partition(newpart['free_minor'], newpart['size'], 0, 0, newpart['type'], mountpoint=newpart['mountpoint'])
175
176 def _megabytes_to_sectors(self, mb, sector_bytes=512):
177 return long(mb * MEGABYTE / sector_bytes)
178
179 def _sectors_to_megabytes(self, sectors, sector_bytes=512):
180 return float((float(sectors) * sector_bytes)/ float(MEGABYTE))
181
182 def _wait_for_device_node(self, devnode):
183 if GLIUtility.is_file("/sbin/udevsettle"):
184 GLIUtility.spawn("/sbin/udevsettle")
185 if not GLIUtility.is_file(devnode):
186 GLIUtility.spawn("/sbin/udevsettle")
187 else:
188 for i in range(0, 10):
189 if GLIUtility.is_file(devnode):
190 break
191 time.sleep(1)
192 time.sleep(1)
193 for i in range(0, 10):
194 if GLIUtility.is_file(devnode):
195 break
196 time.sleep(1)
197
198 ##
199 # Adds a new partition to the partition info
200 # @param freeidx minor of unallocated space partition is being created in
201 # @param mb size of partition in MB
202 # @param type Partition type (ext2, ext3, fat32, linux-swap, free, extended, etc.)
203 def add_partition(self, freeidx, mb, fs, pregap=0):
204 types = { 'primary': parted.PARTITION_PRIMARY, 'extended': parted.PARTITION_EXTENDED, 'logical': parted.PARTITION_LOGICAL }
205 fs_types = {}
206 fstype = None
207 try:
208 free_part = self._partitions[freeidx]
209 except:
210 # raise an exception here
211 pass
212 if mb > free_part['mb']:
213 # raise an exception here
214 pass
215 # Enumerate supported filesystem types
216 fs_type = parted.file_system_type_get_next()
217 while fs_type:
218 fs_types[fs_type.name] = fs_type
219 fs_type = parted.file_system_type_get_next(fs_type)
220 # apple_bootstrap is a "magic" hfs
221 if fs == "apple_bootstrap":
222 fs = "hfs"
223 # grab relevant parted filesystemtype object
224 if fs:
225 fstype = fs_types[fs]
226 # determine correct partition type
227 parttype = "primary"
228 if fs == "extended":
229 fstype = None
230 parttype = "extended"
231 elif free_part.is_logical():
232 parttype = "logical"
233 # figure out start/end sectors
234 start = free_part['start'] + self._megabytes_to_sectors(pregap)
235 end = start + self._megabytes_to_sectors(mb)
236 newpart = self._parted_disk.partition_new(types[parttype], fstype, start, end)
237 constraint = self._parted_disk.dev.constraint_any()
238 constraint.start_range.set_start(start)
239 constraint.start_range.set_end(start)
240 self._parted_disk.add_partition(newpart, constraint)
241 self._parted_disk.commit()
242 self.set_partitions_from_disk()
243
244 ##
245 # Removes partition from partition info
246 # @param minor Minor of partition to remove
247 def delete_partition(self, partidx):
248 try:
249 tmp_part = self._partitions[partidx]
250 except:
251 # raise exception here
252 pass
253 if tmp_part['minor'] < 1:
254 # raise an exception here
255 pass
256 try:
257 self._parted_disk.delete_partition(self._parted_disk.get_partition(tmp_part['minor']))
258 except:
259 # raise an exception here
260 pass
261 self._parted_disk.commit()
262 self.set_partitions_from_disk()
263
264 ##
265 # This function clears the partition table
266 def clear_partitions(self, disklabel=None):
267 if not disklabel:
268 disklabel = archinfo[self._arch]
269 self._parted_disk = self._parted_dev.disk_new_fresh(parted.disk_type_get(disklabel))
270 self._disklabel = disklabel
271 self._parted_disk.commit()
272 self.set_partitions_from_disk()
273
274 ##
275 # Returns an ordered list (disk order) of partition minors
276 def get_ordered_partition_list(self):
277 partlist = []
278 for part in self._partitions:
279 partlist.append(part.get_minor())
280 return partlist
281
282 ##
283 # Returns the minor of the extended partition, if any
284 def get_extended_partition(self):
285 for part in self._partitions:
286 if part.is_extended():
287 return part.get_minor()
288 return 0
289
290 ##
291 # Returns the drive model
292 def get_model(self):
293 return self._parted_dev.model
294
295 ##
296 # Returns the disklabel type
297 def get_disklabel(self):
298 return self._disklabel
299
300 ##
301 # Returns all the geometry information
302 def get_geometry(self):
303 return self._geometry
304
305 ##
306 # Returns partition info dictionary
307 def get_partitions(self):
308 return self._partitions
309
310 ##
311 # This class represents a partition within a GLIStorageDevice object
312 class Partition:
313 "Class representing a single partition within a Device object"
314
315 _device = None
316
317 ##
318 # Initialization function for the Partition class
319 # @param device Parent GLIStorageDevice object
320 # @param parted_part parted.Partition object
321 def __init__(self, device, parted_part):
322 self._device = device
323 self._start = parted_part.geom.start
324 self._end = parted_part.geom.end
325 self._type = ""
326 self._minor = parted_part.minor
327 self._part_mb = float((self._end - self._start + 1) * device._geometry['sector_bytes'] / MEGABYTE)
328 self._part_name = ""
329 self._resizeable = False
330
331 # determine the /dev node that refers to this partition
332 tmpdevice = device.get_device()
333 label_type = device._parted_disk.type.name
334 if label_type == "loop":
335 self._devnode = tmpdevice
336 elif device[-1] in "0123456789":
337 self._devnode = tmpdevice + "p" + str(self._minor)
338 else:
339 self._devnode = tmpdevice + str(self._minor)
340
341 if not parted_part.type_name == "free":
342 if parted_part.fs_type:
343 self._type = parted_part.fs_type.name
344 if self._type == "hfs" and parted_part.is_flag_available(1) and parted_part.get_flag(1):
345 self._type = "apple_bootstrap"
346 else:
347 # Add additional partition identification code here
348 pass
349 if parted_part.type == 2: self._type = "extended"
350 if device._parted_disk.type.check_feature(parted.DISK_TYPE_PARTITION_NAME):
351 self._part_name = parted_part.get_name()
352
353 # The 10 is completely arbitrary. If flags seem to be missed, this number should be increased
354 for flag in range(0, 10):
355 if parted_part.is_flag_available(flag) and parted_part.get_flag(flag):
356 self._flags.append(flag)
357 if type == "ext2" or type == "ext3":
358 block_size = long(string.strip(commands.getoutput("dumpe2fs -h " + self._devnode + r" 2>&1 | grep -e '^Block size:' | sed -e 's/^Block size:\s\+//'")))
359 free_blocks = long(string.strip(commands.getoutput("dumpe2fs -h " + self._devnode + r" 2>&1 | grep -e '^Free blocks:' | sed -e 's/^Free blocks:\s\+//'")))
360 free_bytes = long(block_size * free_blocks)
361 # can't hurt to pad (the +50) it a bit since this is really just a guess
362 self._min_mb_for_resize = self._mb - long(free_bytes / MEGABYTE) + 50
363 self._resizeable = True
364 elif type == "ntfs":
365 min_bytes = long(commands.getoutput("ntfsresize -f --info " + self._devnode + " | grep -e '^You might resize' | sed -e 's/You might resize at //' -e 's/ bytes or .\+//'"))
366 self._min_mb_for_resize = long(min_bytes / MEGABYTE) + 50
367 self._resizeable = True
368 else:
369 try:
370 parted_fs = parted_part.geom.file_system_open()
371 resize_constraint = parted_fs.get_resize_constraint()
372 min_bytes = resize_constraint.min_size * self._device._sector_bytes
373 self._min_mb_for_resize = long(min_bytes / MEGABYTE) + 1
374 self._resizeable = True
375 except:
376 self._resizeable = False
377
378 def __getitem__(self, name):
379 tmpdict = {
380 'start': self.get_start,
381 'end': self.get_end,
382 'type': self.get_type,
383 'minor': self.get_minor,
384 'mb': self.get_mb,
385 'flags': self.get_flags,
386 'name': self.get_name,
387 'devnode': self.get_devnode
388 }
389 if name in tmpdict:
390 return tmpdict[name]()
391 else:
392 raise ValueError(name + " is not a valid attribute!")
393
394 def __setitem__(self, name, value):
395 tmpdict = {
396 'flags': self.set_flags,
397 'name': self.set_name
398 }
399 if name in tmpdict:
400 tmpdict[name](value)
401 else:
402 raise ValueError(name + " is not a valid attribute!")
403
404 ##
405 # Returns the dev node that this partition will have
406 def get_devnode(self):
407 return self._devnode
408
409 ##
410 # Returns whether or not the partition is extended
411 def is_extended(self):
412 if self._type == "extended":
413 return True
414 else:
415 return False
416
417 ##
418 # Returns whether or not the partition is logical
419 def is_logical(self):
420 if self._type == "free":
421 if int(self._minor) + FREE_MINOR_FRAC_LOG == self._minor:
422 return True
423 else:
424 return False
425 elif self._device._labelinfo['extended'] and self._minor > 4:
426 return True
427 else:
428 return False
429
430 ##
431 # Returns a list of logical partitions if this is an extended partition
432 def get_logicals(self):
433 if not self.is_extended():
434 return None
435 logicals = []
436 for part in self._device._partitions:
437 if part.get_minor() > 4 and not part.get_type() == "free":
438 logicals.append(part.get_minor())
439 return logicals
440
441 ##
442 # Returns the start sector for the partition
443 def get_start(self):
444 return long(self._start)
445
446 ##
447 # Returns end sector for the partition
448 def get_end(self):
449 return long(self._end)
450
451 ##
452 # Returns size of partition in MB
453 def get_mb(self):
454 return self._mb
455
456 ##
457 # Returns type of partition
458 def get_type(self):
459 return self._type
460
461 ##
462 # Returns parent GLIStorageDevice object
463 def get_device(self):
464 return self._device
465
466 ##
467 # Returns minor of partition
468 def get_minor(self):
469 return self._minor
470
471 ##
472 # Returns whether the partition is resizeable
473 def is_resizeable(self):
474 return self._resizeable
475
476 ##
477 # Sets partition flags
478 def set_flags(self, flags):
479 self._flags = flags
480
481 ##
482 # Returns partition flags
483 def get_flags(self):
484 return self._flags
485
486 ##
487 # Sets partition name
488 def set_name(self, name):
489 self._name = name
490
491 ##
492 # Returns partition name
493 def get_name(self):
494 return self._name
495
496 ##
497 # Returns minimum MB for resize
498 def get_min_mb_for_resize(self):
499 # if self.is_extended():
500 # min_size = self._start
501 # for part in self._device._partitions:
502 # if part < 5: continue
503 # if part.get_end > min_size: min_size = part.get_end()
504 # return min_size
505 # else:
506 if self._resizeable:
507 return self._min_mb_for_resize
508 else:
509 return -1
510
511 ##
512 # Returns maximum MB for resize
513 def get_max_mb_for_resize(self):
514 if self._resizeable:
515 minor_pos = self._device.get_partition_position(self._minor)
516 try:
517 free_minor = self._device._partitions[minor_pos+1].get_minor()
518 except:
519 free_minor = 0
520 if not free_minor or not self._device.get_partition(free_minor).get_type() == "free": return self._mb
521 if self._device.get_partition(free_minor).is_logical():
522 if self.is_logical():
523 return self._mb + self._device.get_partition(free_minor).get_mb()
524 else:
525 return self._mb
526 else:
527 return self._mb + self._device.get_partition(free_minor).get_mb()
528 else:
529 return -1
530
531 ##
532 # Resizes the partition
533 # @param mb New size in MB
534 def resize(self, mb):
535 minor_pos = self._device.get_partition_position(self._minor)
536 try:
537 free_minor = self._device._partitions[minor_pos+1].get_minor()
538 except:
539 free_minor = 0
540 if mb < self._mb:
541 # Shrinking
542 if not free_minor or not self._device.get_partition(free_minor).get_type() == "free":
543 if self._device._disklabel == "mac":
544 free_minor = self._minor + 1
545 elif self.is_logical():
546 free_minor = self._minor + FREE_MINOR_FRAC_LOG
547 else:
548 free_minor = self._minor + FREE_MINOR_FRAC_PRI
549 if self._device.get_partition(free_minor):
550 for i, part in enumerate(self._device._partitions):
551 if i <= minor_pos or free_minor > part.get_minor(): continue
552 part.set_minor(part.get_minor() + 1)
553 self._device._partitions.insert(minor_pos+1, Partition(self._device, free_minor, self._mb - mb, 0, 0, "free", format=False, existing=False))
554 else:
555 self._device.get_partition(free_minor).set_mb(self._device.get_partition(free_minor).get_mb() + (self._mb - mb))
556 self._mb = mb
557 else:
558 if mb == self._mb + self._device.get_partition(free_minor).get_mb():
559 # Using all available unallocated space
560 self._device._partitions.pop(self._device.get_partition_position(free_minor))
561 self._mb = mb
562 else:
563 # Growing
564 self._device.get_partition(free_minor).set_mb(self._device.get_partition(free_minor).get_mb() - (mb - self._mb))
565 self._mb = mb
566 self._resized = True
567 self._device.tidy_partitions()
568
569 ##
570 # Returns a list of detected partitionable devices
571 def detect_devices():
572 devices = []
573
574 # Make sure sysfs exists
575 # TODO: rewrite for 2.4 support
576 if not os.path.exists("/sys/bus"):
577 raise GLIException("GLIStorageDeviceError", 'fatal', 'detect_devices', "no sysfs found (you MUST use a kernel >2.6)")
578 # Make sure /proc/partitions exists
579 if not os.path.exists("/proc/partitions"):
580 raise GLIException("GLIStorageDeviceError", 'fatal', 'detect_devices', "/proc/partitions does not exist! Please make sure procfs is in your kernel and mounted!")
581
582 # Load /proc/partitions into the variable 'partitions'
583 partitions = []
584 for line in open("/proc/partitions"):
585 tmpparts = line.split()
586 if len(tmpparts) < 4 or not tmpparts[0].isdigit() or not tmpparts[1].isdigit():
587 continue
588
589 # Get the major, minor and device name
590 major = int(tmpparts[0])
591 minor = int(tmpparts[1])
592 device = "/dev/" + tmpparts[3]
593
594 # If there is no /dev/'device_name', then scan
595 # all the devices in /dev to try and find a
596 # devices with the same major and minor
597 if not os.path.exists(device):
598 device = None
599 for path, dirs, files in os.walk("/dev"):
600 for d_file in files:
601 full_file = os.path.join(path, d_file)
602 if not os.path.exists(full_file):
603 continue
604 statres = os.stat(full_file)
605 fmaj = os.major(statres.st_rdev)
606 fmin = os.minor(statres.st_rdev)
607 if fmaj == major and fmin == minor:
608 device = full_file
609 break
610 if not device:
611 continue
612
613 partitions.append(( major, minor, device ))
614
615 # Scan sysfs for the devices of type 'x'
616 # 'x' being a member of the list below:
617 # Compaq cards.../sys/block/{cciss,ida}!cXdX/dev
618 for dev_glob in ("/sys/bus/ide/devices/*/block*/dev", "/sys/bus/scsi/devices/*/block*/dev", "/sys/block/cciss*/dev", "/sys/block/ida*/dev"):
619 sysfs_devices = glob(dev_glob)
620 if not sysfs_devices: continue
621 for sysfs_device in sysfs_devices:
622 # Get the major and minor info
623 try:
624 major, minor = open(sysfs_device).read().split(":")
625 major = int(major)
626 minor = int(minor)
627 except:
628 raise GLIException("GLIStorageDeviceError", 'fatal', 'detect_devices', "invalid major/minor in " + sysfs_device)
629
630 # Find a device listed in /proc/partitions
631 # that has the same minor and major as our
632 # current block device.
633 for record in partitions:
634 if major == record[0] and minor == record[1]:
635 devices.append(record[2])
636
637 # For testing the partitioning code
638 if GLIUtility.is_file("/tmp/disk.img"):
639 devices.append("/tmp/disk.img")
640
641 # We have assembled the list of devices, so return it
642 return devices

Properties

Name Value
svn:eol-style native

  ViewVC Help
Powered by ViewVC 1.1.20