1
/***************************************************************************
2
* Copyright (C) 2008 by Volker Lanz <vl@fidra.de> *
4
* This program is free software; you can redistribute it and/or modify *
5
* it under the terms of the GNU General Public License as published by *
6
* the Free Software Foundation; either version 2 of the License, or *
7
* (at your option) any later version. *
9
* This program is distributed in the hope that it will be useful, *
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12
* GNU General Public License for more details. *
14
* You should have received a copy of the GNU General Public License *
15
* along with this program; if not, write to the *
16
* Free Software Foundation, Inc., *
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18
***************************************************************************/
23
#include "core/partitiontable.h"
24
#include "core/partition.h"
25
#include "core/device.h"
27
#include "fs/filesystem.h"
28
#include "fs/filesystemfactory.h"
30
#include "util/globallog.h"
35
/** Creates a new PartitionTable object with type MSDOS */
36
PartitionTable::PartitionTable() :
44
/** Destroys a PartitionTable object, destroying all children */
45
PartitionTable::~PartitionTable()
50
void PartitionTable::clear()
57
/** Gets the number of free sectors before a given child Partition in this PartitionTable.
59
@param p the Partition for which to get the free sectors before
60
@returns the number of free sectors before the Partition
62
qint64 PartitionTable::freeSectorsBefore(const Partition& p) const
64
const Partition* pred = predecessor(p);
66
if (pred && pred->roles().has(PartitionRole::Unallocated))
67
return pred->length();
72
/** Gets the number of free sectors after a given child Partition in this PartitionTable.
74
@param p the Partition for which to get the free sectors after
75
@returns the number of free sectors after the Partition
77
qint64 PartitionTable::freeSectorsAfter(const Partition& p) const
79
const Partition* succ = successor(p);
81
if (succ && succ->roles().has(PartitionRole::Unallocated))
82
return succ->length();
87
/** @return true if the PartitionTable has an extended Partition */
88
bool PartitionTable::hasExtended() const
90
for (int i = 0; i < children().size(); i++)
91
if (children()[i]->roles().has(PartitionRole::Extended))
97
/** @return pointer to the PartitionTable's extended Partition or NULL if none exists */
98
Partition* PartitionTable::extended()
100
for (int i = 0; i < children().size(); i++)
101
if (children()[i]->roles().has(PartitionRole::Extended))
102
return children()[i];
107
/** Gets valid PartitionRoles for a Partition
108
@param p the Partition
109
@return valid roles for the given Partition
111
PartitionRole::Roles PartitionTable::childRoles(const Partition& p) const
113
Q_ASSERT(p.parent());
115
PartitionRole::Roles r = p.parent()->isRoot() ? PartitionRole::Primary : PartitionRole::Logical;
117
if (r == PartitionRole::Primary && hasExtended() == false)
118
r |= PartitionRole::Extended;
123
/** @return the number of primaries in this PartitionTable */
124
int PartitionTable::numPrimaries() const
128
foreach(const Partition* p, children())
129
if (p->roles().has(PartitionRole::Primary) || p->roles().has(PartitionRole::Extended))
135
/** Appends a Partition to this PartitionTable
136
@param partition pointer of the partition to append. Must not be NULL.
138
void PartitionTable::append(Partition* partition)
140
children().append(partition);
143
/** @param f the flag to get the name for
144
@returns the flags name or an empty QString if the flag is not known
146
QString PartitionTable::flagName(Flag f)
150
case PartitionTable::FlagBoot: return i18nc("@item partition flag", "boot");
151
case PartitionTable::FlagRoot: return i18nc("@item partition flag", "root");
152
case PartitionTable::FlagSwap: return i18nc("@item partition flag", "swap");
153
case PartitionTable::FlagHidden: return i18nc("@item partition flag", "hidden");
154
case PartitionTable::FlagRaid: return i18nc("@item partition flag", "raid");
155
case PartitionTable::FlagLvm: return i18nc("@item partition flag", "lvm");
156
case PartitionTable::FlagLba: return i18nc("@item partition flag", "lba");
157
case PartitionTable::FlagHpService: return i18nc("@item partition flag", "hpservice");
158
case PartitionTable::FlagPalo: return i18nc("@item partition flag", "palo");
159
case PartitionTable::FlagPrep: return i18nc("@item partition flag", "prep");
160
case PartitionTable::FlagMsftReserved: return i18nc("@item partition flag", "msft-reserved");
169
/** @return list of all flags */
170
QList<PartitionTable::Flag> PartitionTable::flagList()
172
QList<PartitionTable::Flag> rval;
174
rval.append(PartitionTable::FlagBoot);
175
rval.append(PartitionTable::FlagRoot);
176
rval.append(PartitionTable::FlagSwap);
177
rval.append(PartitionTable::FlagHidden);
178
rval.append(PartitionTable::FlagRaid);
179
rval.append(PartitionTable::FlagLvm);
180
rval.append(PartitionTable::FlagLba);
181
rval.append(PartitionTable::FlagHpService);
182
rval.append(PartitionTable::FlagPalo);
183
rval.append(PartitionTable::FlagPrep);
184
rval.append(PartitionTable::FlagMsftReserved);
189
/** @param flags the flags to get the names for
190
@returns QStringList of the flags' names
192
QStringList PartitionTable::flagNames(Flags flags)
199
while(!(s = flagName(static_cast<PartitionTable::Flag>(f))).isEmpty())
210
/** Checks if a given Partition on a given Device is snapped to cylinder boundaries.
212
Will print warning messages to GlobalLog if the Partition's first sector is not snapped and
213
another one if the last sector is not snapped.
215
@see snap(), canSnapToSector()
217
@param d the Device the Partition is on
218
@param p the Partition to check
219
@return true if snapped
221
bool PartitionTable::isSnapped(const Device& d, const Partition& p)
223
// don't bother with unallocated space here.
224
if (p.roles().has(PartitionRole::Unallocated))
229
// There are some special cases for snapping partitions to cylinder boundaries, apparently.
230
// 1) If an extended partition starts at the beginning of the device (that would be sector 63
231
// on modern drives, equivalent to sectorsPerTrack() in any case), the first logical partition
232
// at the beginning of this extended partition starts at 2 * sectorsPerTrack().
233
// 2) If a primary or extended starts at the beginning of a device, it starts at sectorsPerTrack().
234
// 3) Any logical partition is always preceded by the extended partition table entry in the
235
// sectorsPerTrack() before it, so it's always sectorsPerTrack() "late"
236
if (p.roles().has(PartitionRole::Logical) && p.firstSector() == 2 * d.sectorsPerTrack())
237
delta = (p.firstSector() - (2 * d.sectorsPerTrack())) % d.cylinderSize();
238
else if (p.roles().has(PartitionRole::Logical) || p.firstSector() == d.sectorsPerTrack())
239
delta = (p.firstSector() - d.sectorsPerTrack()) % d.cylinderSize();
241
delta = p.firstSector() % d.cylinderSize();
247
log(log::warning) << i18nc("@info/plain", "Partition <filename>%1</filename> does not start at a cylinder boundary (first sector: %2, modulo: %3).", p.deviceNode(), p.firstSector(), delta);
251
delta = (p.lastSector() + 1) % d.cylinderSize();
255
log(log::warning) << i18nc("@info/plain", "Partition <filename>%1</filename> does not end at a cylinder boundary (last sector: %2, modulo: %3).", p.deviceNode(), p.lastSector(), delta);
262
/** Checks if a Partition can be snapped to a given sector on a given Device.
264
@see PartitionTable::snap(), PartitionTable::isSnapped()
266
@param d the Device the Partition is on
267
@param p the Partition to snap
268
@param s the sector to snap to
269
@param originalPartition pointer to another Partition @p p has just been copied from or NULL
270
@return true if snapping to @p s is possible
272
static bool canSnapToSector(const Device& d, const Partition& p, qint64 s, const Partition* originalPartition)
274
Q_ASSERT(d.partitionTable());
276
if (s < d.sectorsPerTrack() || s >= d.totalSectors())
279
const Partition* other = d.partitionTable()->findPartitionBySector(s, PartitionRole(PartitionRole::Logical | PartitionRole::Primary | PartitionRole::Extended | PartitionRole::Unallocated));
281
if (other && other->roles().has(PartitionRole::Unallocated))
284
return other == NULL || other == &p || other == originalPartition;
287
/** Snaps the given Partition on the given Device to cylinder boundaries.
289
Tries under all accounts to keep the Partition's length equal to the original length or
290
to increase it, if that is not possible. Will print a warning message to GlobalLog if
291
this is not possible.
293
The parameter @p originalPartition is required for cases where a Partition has just been
294
duplicated to resize or move it. This method needs to know the original because of course
295
the original does not prevent snapping to any sector allocated by it.
297
@see canSnapToSector(), isSnapped()
299
@param d the Device the Partition is on
300
@param p the Partition to snap
301
@param originalPartition pointer to a Partition object @p p has just been copied from or NULL
302
@return true if Partition is now snapped to cylinder boundaries
304
bool PartitionTable::snap(const Device& d, Partition& p, const Partition* originalPartition)
306
const qint64 originalLength = p.length();
308
bool lengthIsSnapped = false;
310
// This is the same as in isSnapped(), only we additionally have to remember if the
311
// partition's _length_ is "snapped", so to speak (i.e., evenly divisable by
312
// the cylinder size)
313
if (p.roles().has(PartitionRole::Logical) && p.firstSector() == 2 * d.sectorsPerTrack())
315
delta = (p.firstSector() - (2 * d.sectorsPerTrack())) % d.cylinderSize();
316
lengthIsSnapped = (p.length() + (2 * d.sectorsPerTrack())) % d.cylinderSize() == 0;
318
else if (p.roles().has(PartitionRole::Logical) || p.firstSector() == d.sectorsPerTrack())
320
delta = (p.firstSector() - d.sectorsPerTrack()) % d.cylinderSize();
321
lengthIsSnapped = (p.length() + d.sectorsPerTrack()) % d.cylinderSize() == 0;
325
delta = p.firstSector() % d.cylinderSize();
326
lengthIsSnapped = p.length() % d.cylinderSize() == 0;
331
/** @todo Don't assume we always want to snap to the front.
332
Always trying to snap to the front solves the problem that a partition does
333
get too small to take another one that's copied to it, but it introduces
334
a new bug: The user might create a partition aligned at the end of a device,
335
extended partition or at the start of the next one, but we snap to the back
336
and leave some space in between.
338
// We always want to make the partition larger, not smaller. Making it smaller
339
// might, in case it's a partition that another is being copied to, mean the partition
340
// ends up too small. So try to move the start to the front first.
341
qint64 snappedFirst = p.firstSector() - delta;
343
// Now if the cylinder boundary at the front is occupied...
344
if (!canSnapToSector(d, p, p.firstSector() - delta, originalPartition))
346
// ... move to the cylinder towards the end of the device ...
347
snappedFirst += d.cylinderSize();
349
// ... and move the end of the partition towards the end, too, if that is possible.
350
// By doing this, we still try to keep the length >= the original length. If the
351
// last sector ends up not being on a cylinder boundary by doing so, the code
352
// below will deal with that.
353
qint64 numTooShort = d.cylinderSize() - delta;
354
if (canSnapToSector(d, p, p.lastSector() + numTooShort, originalPartition))
356
p.setLastSector(p.lastSector() + numTooShort);
357
p.fileSystem().setLastSector(p.fileSystem().lastSector() + numTooShort);
361
p.setFirstSector(snappedFirst);
362
p.fileSystem().setFirstSector(snappedFirst);
365
delta = (p.lastSector() + 1) % d.cylinderSize();
369
// Try to snap to the back first...
370
qint64 snappedLast = p.lastSector() + d.cylinderSize() - delta;
372
// .. but if we can retain the partition length exactly by snapping to the front ...
373
if (lengthIsSnapped && p.length() - originalLength == delta)
374
snappedLast -= d.cylinderSize();
375
// ... or if there's something there already, snap to the front.
376
else if (!canSnapToSector(d, p, snappedLast, originalPartition))
377
snappedLast -= d.cylinderSize();
379
p.setLastSector(snappedLast);
380
p.fileSystem().setLastSector(snappedLast);
383
// Now, did we make the partition too big for its file system?
384
while (p.length() > originalLength && p.capacity() > p.fileSystem().maxCapacity() && canSnapToSector(d, p, p.lastSector() - d.cylinderSize(), originalPartition))
386
p.setLastSector(p.lastSector() - d.cylinderSize());
387
p.fileSystem().setLastSector(p.fileSystem().lastSector() - d.cylinderSize());
390
if (p.length() < originalLength)
391
log(log::warning) << i18nc("@info/plain", "The partition cannot be created with the requested length of %1 sectors and will instead only be %2 sectors long.", originalLength, p.length());
393
// In an extended partition we also need to snap unallocated children at the beginning and at the end
394
// (there should never be a need to snap non-unallocated children)
395
if (p.roles().has(PartitionRole::Extended))
397
if (p.children().size() > 0)
399
if (p.children().first()->roles().has(PartitionRole::Unallocated))
401
p.children().first()->setFirstSector(p.firstSector() + d.sectorsPerTrack());
402
p.children().first()->fileSystem().setFirstSector(p.fileSystem().firstSector() + d.sectorsPerTrack());
405
if (p.children().last()->roles().has(PartitionRole::Unallocated))
407
p.children().last()->setLastSector(p.lastSector());
408
p.children().last()->fileSystem().setLastSector(p.fileSystem().lastSector());
413
return isSnapped(d, p);
416
/** Creates a new unallocated Partition on the given Device.
417
@param device the Device to create the new Partition on
418
@param parent the parent PartitionNode for the new Partition
419
@param start the new Partition's start sector
420
@param end the new Partition's end sector
421
@return pointer to the newly created Partition object or NULL if the Partition could not be created
423
Partition* createUnallocated(const Device& device, PartitionNode& parent, qint64 start, qint64 end)
425
PartitionRole::Roles r = PartitionRole::Unallocated;
427
if (!parent.isRoot())
429
Partition* extended = dynamic_cast<Partition*>(&parent);
431
if (extended == NULL)
433
kWarning() << "extended is null. start: " << start << ", end: " << end << ", device: " << device.deviceNode();
439
// Leave a track free at the start for a new partition's metadata
440
start += device.sectorsPerTrack();
442
// .. and also at the end for the metadata for a partition to follow us, if we're not
443
// at the end of the extended partition
444
if (end < extended->lastSector())
445
end -= device.sectorsPerTrack();
447
r |= PartitionRole::Logical;
450
if (end - start + 1 < device.cylinderSize())
453
return new Partition(&parent, device, PartitionRole(r), FileSystemFactory::create(FileSystem::Unknown, start, end), start, end, -1);
456
/** Removes all unallocated children from a PartitionNode
457
@param p pointer to the parent to remove unallocated children from
459
void PartitionTable::removeUnallocated(PartitionNode* p)
465
while (i < p->children().size())
467
Partition* child = p->children()[i];
469
if (child->roles().has(PartitionRole::Unallocated))
475
if (child->roles().has(PartitionRole::Extended))
476
removeUnallocated(child);
485
void PartitionTable::removeUnallocated()
487
removeUnallocated(this);
490
/** Inserts unallocated children for a Device's PartitionTable with the given parent.
492
This method inserts unallocated Partitions for a parent, usually the Device this
493
PartitionTable is on. It will also insert unallocated Partitions in any extended
496
@warning This method assumes that no unallocated Partitions exist when it is called.
498
@param d the Device this PartitionTable and @p p are on
499
@param p the parent PartitionNode (may be this or an extended Partition)
500
@param start the first sector to begin looking for free space
502
void PartitionTable::insertUnallocated(const Device& d, PartitionNode* p, qint64 start) const
506
qint64 lastEnd = start;
508
foreach (Partition* child, p->children())
510
p->insert(createUnallocated(d, *p, lastEnd, child->firstSector() - 1));
512
if (child->roles().has(PartitionRole::Extended))
513
insertUnallocated(d, child, child->firstSector());
515
lastEnd = child->lastSector() + 1;
518
// Take care of the free space between the end of the last child and the end
519
// of the device or the extended partition.
520
qint64 parentEnd = d.totalSectors() - 1;
524
Partition* extended = dynamic_cast<Partition*>(p);
525
Q_ASSERT(extended != NULL);
526
parentEnd = (extended != NULL) ? extended->lastSector() : -1;
529
if (parentEnd >= d.cylinderSize())
530
p->insert(createUnallocated(d, *p, lastEnd, parentEnd));
533
/** Updates the unallocated Partitions for this PartitionTable.
534
@param d the Device this PartitionTable is on
536
void PartitionTable::updateUnallocated(const Device& d)
539
insertUnallocated(d, this, d.sectorsPerTrack());
542
void PartitionTable::setTypeName(const QString& s)
547
The application currently is not prepared to correctly handle any other disk label
548
type than msdos. Until that situation changes, all disk labels but msdos are
551
setReadOnly(typeName() != "msdos");