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 }