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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:eol-style native

  ViewVC Help
Powered by ViewVC 1.1.20