File indexing completed on 2024-04-28 05:45:46
0001 /* 0002 SPDX-FileCopyrightText: 2008-2012 Volker Lanz <vl@fidra.de> 0003 SPDX-FileCopyrightText: 2009 Andrew Coles <andrew.i.coles@googlemail.com> 0004 SPDX-FileCopyrightText: 2013-2020 Andrius Štikonas <andrius@stikonas.eu> 0005 SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org> 0006 SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com> 0007 SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com> 0008 0009 SPDX-License-Identifier: GPL-3.0-or-later 0010 */ 0011 0012 /** @file 0013 */ 0014 0015 #include "core/partitiontable.h" 0016 #include "core/partition.h" 0017 #include "core/device.h" 0018 #include "core/diskdevice.h" 0019 #include "core/lvmdevice.h" 0020 #include "core/partitionalignment.h" 0021 #include "fs/filesystem.h" 0022 #include "fs/filesystemfactory.h" 0023 0024 #include "util/globallog.h" 0025 0026 #include <utility> 0027 0028 #include <KLocalizedString> 0029 0030 #include <QDebug> 0031 #include <QTextStream> 0032 0033 /** Creates a new PartitionTable object with type MSDOS 0034 @param type name of the PartitionTable type (e.g. "msdos" or "gpt") 0035 */ 0036 PartitionTable::PartitionTable(TableType type, qint64 firstUsable, qint64 lastUsable) 0037 : PartitionNode() 0038 , m_Children() 0039 , m_MaxPrimaries(maxPrimariesForTableType(type)) 0040 , m_Type(type) 0041 , m_FirstUsable(firstUsable) 0042 , m_LastUsable(lastUsable) 0043 { 0044 } 0045 0046 /** Copy constructor for PartitionTable. 0047 * @param other the other PartitionTable. 0048 */ 0049 PartitionTable::PartitionTable(const PartitionTable& other) 0050 : PartitionNode() 0051 , m_Children() 0052 , m_MaxPrimaries(other.m_MaxPrimaries) 0053 , m_Type(other.m_Type) 0054 , m_FirstUsable(other.m_FirstUsable) 0055 , m_LastUsable(other.m_LastUsable) 0056 { 0057 for (Partitions::const_iterator it = other.m_Children.constBegin(); 0058 it != other.m_Children.constEnd(); ++it) 0059 { 0060 m_Children.append(new Partition(**it, this)); 0061 } 0062 } 0063 0064 /** Destroys a PartitionTable object, destroying all children */ 0065 PartitionTable::~PartitionTable() 0066 { 0067 clearChildren(); 0068 } 0069 0070 /** Gets the number of free sectors before a given child Partition in this PartitionTable. 0071 0072 @param p the Partition for which to get the free sectors before 0073 @returns the number of free sectors before the Partition 0074 */ 0075 qint64 PartitionTable::freeSectorsBefore(const Partition& p) const 0076 { 0077 const Partition* pred = predecessor(p); 0078 0079 // due to the space required for extended boot records the 0080 // below is NOT the same as pred->length() 0081 if (pred && pred->roles().has(PartitionRole::Unallocated)) 0082 return p.firstSector() - pred->firstSector(); 0083 0084 return 0; 0085 } 0086 0087 /** Gets the number of free sectors after a given child Partition in this PartitionTable. 0088 0089 @param p the Partition for which to get the free sectors after 0090 @returns the number of free sectors after the Partition 0091 */ 0092 qint64 PartitionTable::freeSectorsAfter(const Partition& p) const 0093 { 0094 const Partition* succ = successor(p); 0095 0096 // due to the space required for extended boot records the 0097 // below is NOT the same as succ->length() 0098 if (succ && succ->roles().has(PartitionRole::Unallocated)) 0099 return succ->lastSector() - p.lastSector(); 0100 0101 return 0; 0102 } 0103 0104 qint64 PartitionTable::freeSectors() const 0105 { 0106 qint64 sectors = 0; 0107 for (const auto &p : children()) { 0108 if (p->roles().has(PartitionRole::Unallocated)) { 0109 sectors += p->length(); 0110 } 0111 } 0112 0113 return sectors; 0114 } 0115 0116 /** @return true if the PartitionTable has an extended Partition */ 0117 bool PartitionTable::hasExtended() const 0118 { 0119 for (const auto &p : children()) 0120 if (p->roles().has(PartitionRole::Extended)) 0121 return true; 0122 0123 return false; 0124 } 0125 0126 /** @return pointer to the PartitionTable's extended Partition or nullptr if none exists */ 0127 Partition* PartitionTable::extended() const 0128 { 0129 for (const auto &p : children()) 0130 if (p->roles().has(PartitionRole::Extended)) 0131 return p; 0132 0133 return nullptr; 0134 } 0135 0136 /** Gets valid PartitionRoles for a Partition 0137 @param p the Partition 0138 @return valid roles for the given Partition 0139 */ 0140 PartitionRole::Roles PartitionTable::childRoles(const Partition& p) const 0141 { 0142 Q_ASSERT(p.parent()); 0143 0144 PartitionRole::Roles r = p.parent()->isRoot() ? PartitionRole::Primary : PartitionRole::Logical; 0145 0146 if (r == PartitionRole::Primary && hasExtended() == false && tableTypeSupportsExtended(type())) 0147 r |= PartitionRole::Extended; 0148 0149 return r; 0150 } 0151 0152 /** @return the number of primaries in this PartitionTable */ 0153 int PartitionTable::numPrimaries() const 0154 { 0155 int result = 0; 0156 0157 for (const auto &p : children()) 0158 if (p->roles().has(PartitionRole::Primary) || p->roles().has(PartitionRole::Extended)) 0159 result++; 0160 0161 return result; 0162 } 0163 0164 /** Appends a Partition to this PartitionTable 0165 @param partition pointer of the partition to append. Must not be nullptr. 0166 */ 0167 void PartitionTable::append(Partition* partition) 0168 { 0169 children().append(partition); 0170 std::sort(children().begin(), children().end(), [] (const Partition *a, const Partition *b) -> bool {return a->firstSector() < b->firstSector();}); 0171 } 0172 0173 /** @param f the flag to get the name for 0174 @returns the flags name or an empty QString if the flag is not known 0175 */ 0176 QString PartitionTable::flagName(Flag f) 0177 { 0178 switch (f) { 0179 case PartitionTable::Flag::Boot: 0180 return xi18nc("@item partition flag", "boot"); 0181 case PartitionTable::Flag::Root: 0182 return xi18nc("@item partition flag", "root"); 0183 case PartitionTable::Flag::Swap: 0184 return xi18nc("@item partition flag", "swap"); 0185 case PartitionTable::Flag::Hidden: 0186 return xi18nc("@item partition flag", "hidden"); 0187 case PartitionTable::Flag::Raid: 0188 return xi18nc("@item partition flag", "raid"); 0189 case PartitionTable::Flag::Lvm: 0190 return xi18nc("@item partition flag", "lvm"); 0191 case PartitionTable::Flag::Lba: 0192 return xi18nc("@item partition flag", "lba"); 0193 case PartitionTable::Flag::HpService: 0194 return xi18nc("@item partition flag", "hpservice"); 0195 case PartitionTable::Flag::Palo: 0196 return xi18nc("@item partition flag", "palo"); 0197 case PartitionTable::Flag::Prep: 0198 return xi18nc("@item partition flag", "prep"); 0199 case PartitionTable::Flag::MsftReserved: 0200 return xi18nc("@item partition flag", "msft-reserved"); 0201 case PartitionTable::Flag::BiosGrub: 0202 return xi18nc("@item partition flag", "bios-grub"); 0203 case PartitionTable::Flag::AppleTvRecovery: 0204 return xi18nc("@item partition flag", "apple-tv-recovery"); 0205 case PartitionTable::Flag::Diag: 0206 return xi18nc("@item partition flag", "diag"); 0207 case PartitionTable::Flag::LegacyBoot: 0208 return xi18nc("@item partition flag", "legacy-boot"); 0209 case PartitionTable::Flag::MsftData: 0210 return xi18nc("@item partition flag", "msft-data"); 0211 case PartitionTable::Flag::Irst: 0212 return xi18nc("@item partition flag", "irst"); 0213 default: 0214 break; 0215 } 0216 0217 return QString(); 0218 } 0219 0220 /** @return list of all flags */ 0221 const QList<PartitionTable::Flag> PartitionTable::flagList() 0222 { 0223 QList<PartitionTable::Flag> rval; 0224 0225 rval.append(PartitionTable::Flag::Boot); 0226 rval.append(PartitionTable::Flag::Root); 0227 rval.append(PartitionTable::Flag::Swap); 0228 rval.append(PartitionTable::Flag::Hidden); 0229 rval.append(PartitionTable::Flag::Raid); 0230 rval.append(PartitionTable::Flag::Lvm); 0231 rval.append(PartitionTable::Flag::Lba); 0232 rval.append(PartitionTable::Flag::HpService); 0233 rval.append(PartitionTable::Flag::Palo); 0234 rval.append(PartitionTable::Flag::Prep); 0235 rval.append(PartitionTable::Flag::MsftReserved); 0236 rval.append(PartitionTable::Flag::BiosGrub); 0237 rval.append(PartitionTable::Flag::AppleTvRecovery); 0238 rval.append(PartitionTable::Flag::Diag); 0239 rval.append(PartitionTable::Flag::LegacyBoot); 0240 rval.append(PartitionTable::Flag::MsftData); 0241 rval.append(PartitionTable::Flag::Irst); 0242 0243 return rval; 0244 } 0245 0246 /** @param flags the flags to get the names for 0247 @returns QStringList of the flags' names 0248 */ 0249 QStringList PartitionTable::flagNames(Flags flags) 0250 { 0251 QStringList rval; 0252 0253 int f = 1; 0254 0255 QString s; 0256 while (!(s = flagName(static_cast<PartitionTable::Flag>(f))).isEmpty()) { 0257 if (flags & f) 0258 rval.append(s); 0259 0260 f <<= 1; 0261 } 0262 0263 return rval; 0264 } 0265 0266 /** @param list QStringList of the flags' names 0267 @returns flags corresponding to names 0268 */ 0269 PartitionTable::Flags PartitionTable::flagsFromList(const QStringList list) 0270 { 0271 Flags flags; 0272 0273 for (const auto &flag : flagList()) 0274 if (list.contains(flagName(flag))) 0275 flags.setFlag(flag); 0276 0277 return flags; 0278 } 0279 0280 bool PartitionTable::getUnallocatedRange(const Device& d, PartitionNode& parent, qint64& start, qint64& end) 0281 { 0282 if (d.type() == Device::Type::Disk_Device) { 0283 const DiskDevice& device = dynamic_cast<const DiskDevice&>(d); 0284 if (!parent.isRoot()) { 0285 Partition* extended = dynamic_cast<Partition*>(&parent); 0286 0287 if (extended == nullptr) { 0288 qWarning() << "extended is null. start: " << start << ", end: " << end << ", device: " << device.deviceNode(); 0289 return false; 0290 } 0291 0292 // Leave a track (cylinder aligned) or sector alignment sectors (sector based) free at the 0293 // start for a new partition's metadata 0294 start += device.partitionTable()->type() == PartitionTable::msdos ? device.sectorsPerTrack() : PartitionAlignment::sectorAlignment(device); 0295 0296 // .. and also at the end for the metadata for a partition to follow us, if we're not 0297 // at the end of the extended partition 0298 if (end < extended->lastSector()) 0299 end -= device.partitionTable()->type() == PartitionTable::msdos ? device.sectorsPerTrack() : PartitionAlignment::sectorAlignment(device); 0300 } 0301 0302 return end - start + 1 >= PartitionAlignment::sectorAlignment(device); 0303 } else if (d.type() == Device::Type::LVM_Device || d.type() == Device::Type::SoftwareRAID_Device) { 0304 if (end - start + 1 > 0) { 0305 return true; 0306 } 0307 } 0308 return false; 0309 } 0310 0311 0312 /** Creates a new unallocated Partition on the given Device. 0313 @param device the Device to create the new Partition on 0314 @param parent the parent PartitionNode for the new Partition 0315 @param start the new Partition's start sector 0316 @param end the new Partition's end sector 0317 @return pointer to the newly created Partition object or nullptr if the Partition could not be created 0318 */ 0319 Partition* createUnallocated(const Device& device, PartitionNode& parent, qint64 start, qint64 end) 0320 { 0321 PartitionRole::Roles r = PartitionRole::Unallocated; 0322 0323 if (!parent.isRoot()) 0324 r |= PartitionRole::Logical; 0325 0326 // Mark unallocated space in LVM VG as LVM LV so that pasting can be easily disabled (it does not work yet) 0327 if (device.type() == Device::Type::LVM_Device) 0328 r |= PartitionRole::Lvm_Lv; 0329 0330 if (!PartitionTable::getUnallocatedRange(device, parent, start, end)) 0331 return nullptr; 0332 0333 return new Partition(&parent, device, PartitionRole(r), FileSystemFactory::create(FileSystem::Type::Unknown, start, end, device.logicalSize()), start, end, QString()); 0334 } 0335 0336 /** Removes all unallocated children from a PartitionNode 0337 @param p pointer to the parent to remove unallocated children from 0338 */ 0339 void PartitionTable::removeUnallocated(PartitionNode* p) 0340 { 0341 Q_ASSERT(p); 0342 0343 qint32 i = 0; 0344 0345 while (i < p->children().size()) { 0346 Partition* child = p->children()[i]; 0347 0348 if (child->roles().has(PartitionRole::Unallocated)) { 0349 p->remove(child); 0350 delete child; 0351 continue; 0352 } 0353 0354 if (child->roles().has(PartitionRole::Extended)) 0355 removeUnallocated(child); 0356 0357 i++; 0358 } 0359 } 0360 0361 /** 0362 @overload 0363 */ 0364 void PartitionTable::removeUnallocated() 0365 { 0366 removeUnallocated(this); 0367 } 0368 0369 /** Inserts unallocated children for a Device's PartitionTable with the given parent. 0370 0371 This method inserts unallocated Partitions for a parent, usually the Device this 0372 PartitionTable is on. It will also insert unallocated Partitions in any extended 0373 Partitions it finds. 0374 0375 @warning This method assumes that no unallocated Partitions exist when it is called. 0376 0377 @param d the Device this PartitionTable and @p p are on 0378 @param p the parent PartitionNode (may be this or an extended Partition) 0379 @param start the first sector to begin looking for free space 0380 */ 0381 void PartitionTable::insertUnallocated(const Device& d, PartitionNode* p, qint64 start) 0382 { 0383 Q_ASSERT(p); 0384 0385 qint64 lastEnd = start; 0386 0387 if (d.type() == Device::Type::LVM_Device && !p->children().isEmpty()) { 0388 // rearranging the sectors of all partitions to keep unallocated space at the end 0389 lastEnd = 0; 0390 std::sort(children().begin(), children().end(), [](const Partition* p1, const Partition* p2) { return p1->deviceNode() < p2->deviceNode(); }); 0391 for (const auto &child : children()) { 0392 qint64 totalSectors = child->length(); 0393 child->setFirstSector(lastEnd); 0394 child->setLastSector(lastEnd + totalSectors - 1); 0395 0396 lastEnd += totalSectors; 0397 } 0398 } else { 0399 const auto pChildren = p->children(); 0400 for (const auto &child : pChildren) { 0401 p->insert(createUnallocated(d, *p, lastEnd, child->firstSector() - 1)); 0402 0403 if (child->roles().has(PartitionRole::Extended)) 0404 insertUnallocated(d, child, child->firstSector()); 0405 0406 lastEnd = child->lastSector() + 1; 0407 } 0408 } 0409 0410 if (d.type() == Device::Type::LVM_Device) 0411 { 0412 const LvmDevice& lvm = static_cast<const LvmDevice&>(d); 0413 p->insert(createUnallocated(d, *p, lastEnd, lastEnd + lvm.freePE() - 1)); 0414 } 0415 else 0416 { 0417 // Take care of the free space between the end of the last child and the end 0418 // of the device or the extended partition. 0419 qint64 parentEnd = lastUsable(); 0420 0421 if (!p->isRoot()) { 0422 Partition* extended = dynamic_cast<Partition*>(p); 0423 parentEnd = extended ? extended->lastSector() : -1; 0424 Q_ASSERT(extended); 0425 } 0426 0427 if (parentEnd >= firstUsable() && parentEnd >= lastEnd) 0428 p->insert(createUnallocated(d, *p, lastEnd, parentEnd)); 0429 } 0430 } 0431 0432 /** Updates the unallocated Partitions for this PartitionTable. 0433 @param d the Device this PartitionTable is on 0434 */ 0435 void PartitionTable::updateUnallocated(const Device& d) 0436 { 0437 removeUnallocated(); 0438 insertUnallocated(d, this, firstUsable()); 0439 } 0440 0441 qint64 PartitionTable::defaultFirstUsable(const Device& d, TableType t) 0442 { 0443 Q_UNUSED(t) 0444 if (d.type() == Device::Type::LVM_Device || d.type() == Device::Type::SoftwareRAID_Device || t == PartitionTable::TableType::none) { 0445 return 0; 0446 } 0447 0448 const DiskDevice& diskDevice = dynamic_cast<const DiskDevice&>(d); 0449 return PartitionAlignment::sectorAlignment(diskDevice); 0450 } 0451 0452 qint64 PartitionTable::defaultLastUsable(const Device& d, TableType t) 0453 { 0454 if (t == gpt) 0455 return d.totalLogical() - 1 - 32 - 1; 0456 0457 return d.totalLogical() - 1; 0458 } 0459 0460 static struct { 0461 const QLatin1String name; /**< name of partition table type */ 0462 quint32 maxPrimaries; /**< max numbers of primary partitions supported */ 0463 bool canHaveExtended; /**< does partition table type support extended partitions */ 0464 bool isReadOnly; /**< does KDE Partition Manager support this only in read only mode */ 0465 PartitionTable::TableType type; /**< enum type */ 0466 } tableTypes[] = { 0467 { QLatin1String("aix"), 4, false, true, PartitionTable::TableType::aix }, 0468 { QLatin1String("bsd"), 8, false, true, PartitionTable::TableType::bsd }, 0469 { QLatin1String("dasd"), 1, false, true, PartitionTable::TableType::dasd }, 0470 { QLatin1String("msdos"), 4, true, false, PartitionTable::TableType::msdos }, 0471 { QLatin1String("msdos"), 4, true, false, PartitionTable::TableType::msdos_sectorbased }, 0472 { QLatin1String("dos"), 4, true, false, PartitionTable::TableType::msdos_sectorbased }, 0473 { QLatin1String("dvh"), 16, true, true, PartitionTable::TableType::dvh }, 0474 { QLatin1String("gpt"), 128, false, false, PartitionTable::TableType::gpt }, 0475 { QLatin1String("loop"), 1, false, true, PartitionTable::TableType::loop }, 0476 { QLatin1String("mac"), 0xffff, false, true, PartitionTable::TableType::mac }, 0477 { QLatin1String("pc98"), 16, false, true, PartitionTable::TableType::pc98 }, 0478 { QLatin1String("amiga"), 128, false, true, PartitionTable::TableType::amiga }, 0479 { QLatin1String("sun"), 8, false, true, PartitionTable::TableType::sun }, 0480 { QLatin1String("vmd"), 0xffff, false, false, PartitionTable::TableType::vmd }, 0481 { QLatin1String("none"), 1, false, false, PartitionTable::TableType::none }, 0482 }; 0483 0484 PartitionTable::TableType PartitionTable::nameToTableType(const QString& n) 0485 { 0486 for (const auto &type : tableTypes) 0487 if (n == type.name) 0488 return type.type; 0489 0490 return PartitionTable::TableType::unknownTableType; 0491 } 0492 0493 QString PartitionTable::tableTypeToName(TableType l) 0494 { 0495 for (const auto &type : tableTypes) 0496 if (l == type.type) 0497 return type.name; 0498 0499 return xi18nc("@item partition table name", "unknown"); 0500 } 0501 0502 qint32 PartitionTable::maxPrimariesForTableType(TableType l) 0503 { 0504 for (const auto &type : tableTypes) 0505 if (l == type.type) 0506 return type.maxPrimaries; 0507 0508 return 1; 0509 } 0510 0511 bool PartitionTable::tableTypeSupportsExtended(TableType l) 0512 { 0513 for (const auto &type : tableTypes) 0514 if (l == type.type) 0515 return type.canHaveExtended; 0516 0517 return false; 0518 } 0519 0520 bool PartitionTable::tableTypeIsReadOnly(TableType l) 0521 { 0522 for (const auto &type : tableTypes) 0523 if (l == type.type) 0524 return type.isReadOnly; 0525 0526 return false; 0527 } 0528 0529 /** Simple heuristic to determine if the PartitionTable is sector aligned (i.e. 0530 if its Partitions begin at sectors evenly divisable by PartitionAlignment::sectorAlignment(). 0531 @return true if is sector aligned, otherwise false 0532 */ 0533 bool PartitionTable::isSectorBased(const Device& d) const 0534 { 0535 if (d.type() == Device::Type::Disk_Device) { 0536 const DiskDevice& diskDevice = dynamic_cast<const DiskDevice&>(d); 0537 0538 if (type() == PartitionTable::msdos) { 0539 // the default for empty partition tables is sector based 0540 if (numPrimaries() == 0) 0541 return true; 0542 0543 quint32 numCylinderAligned = 0; 0544 quint32 numSectorAligned = 0; 0545 0546 // see if we have more cylinder aligned partitions than sector 0547 // aligned ones. 0548 for (const auto &p : children()) { 0549 if (p->firstSector() % PartitionAlignment::sectorAlignment(diskDevice) == 0) 0550 numSectorAligned++; 0551 else if (p->firstSector() % diskDevice.cylinderSize() == 0) 0552 numCylinderAligned++; 0553 } 0554 0555 return numSectorAligned >= numCylinderAligned; 0556 } 0557 return type() == PartitionTable::msdos_sectorbased; 0558 } 0559 0560 return false; 0561 } 0562 0563 void PartitionTable::setType(const Device& d, TableType t) 0564 { 0565 setFirstUsableSector(defaultFirstUsable(d, t)); 0566 setLastUsableSector(defaultLastUsable(d, t)); 0567 0568 m_Type = t; 0569 0570 updateUnallocated(d); 0571 } 0572 0573 QTextStream& operator<<(QTextStream& stream, const PartitionTable& ptable) 0574 { 0575 stream << "type: \"" << ptable.typeName() << "\"\n" 0576 << "align: \"" << (ptable.type() == PartitionTable::msdos ? "cylinder" : "sector") << "\"\n" 0577 << "\n# number start end type roles label flags\n"; 0578 0579 QList<const Partition*> partitions; 0580 0581 for (const auto &p : ptable.children()) { 0582 if (!p->roles().has(PartitionRole::Unallocated)) { 0583 partitions.append(p); 0584 0585 if (p->roles().has(PartitionRole::Extended)) { 0586 const auto partChildren = p->children(); 0587 for (const auto &child : partChildren) { 0588 if (!child->roles().has(PartitionRole::Unallocated)) 0589 partitions.append(child); 0590 } 0591 } 0592 } 0593 } 0594 0595 std::sort(partitions.begin(), partitions.end(), [](const Partition* p1, const Partition* p2) { return p1->number() < p2->number(); }); 0596 0597 for (const auto &p : std::as_const(partitions)) 0598 stream << *p; 0599 0600 return stream; 0601 }