1 /*
2     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
3     SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org>
4     SPDX-FileCopyrightText: 2013-2020 Andrius Štikonas <andrius@stikonas.eu>
5     SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org>
6     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
7 
8     SPDX-License-Identifier: GPL-3.0-or-later
9 */
10 
11 #include "core/partition.h"
12 
13 #include "core/device.h"
14 
15 #include "fs/filesystem.h"
16 #include "fs/filesystemfactory.h"
17 #include "fs/luks.h"
18 
19 #include "util/externalcommand.h"
20 #include "util/report.h"
21 
22 #include <QFile>
23 #include <QRegularExpression>
24 #include <QStorageInfo>
25 #include <QString>
26 #include <QStringList>
27 #include <QTextStream>
28 
29 #include <KLocalizedString>
30 
31 /** Creates a new Partition object.
32     @param parent the Partition's parent. May be another Partition (for logicals) or a PartitionTable. Must not be nullptr.
33     @param device the Device this Partition is on.
34     @param role the Partition's role(s)
35     @param fs pointer to the Partition's FileSystem object. The Partition object will take ownership of this.
36     @param sectorStart the first sector of the Partition on its Device
37     @param sectorEnd the last sector of the Partition on its Device
38     @param partitionPath the Partition's path, e.g. /dev/sda4 or /dev/mmcblk0p1
39     @param availableFlags the flags available for this Partition
40     @param mountPoint mount point for this Partition
41     @param mounted true if the Partition is mounted
42     @param activeFlags active flags for this Partition
43     @param state the Partition's state
44 */
Partition(PartitionNode * parent,const Device & device,const PartitionRole & role,FileSystem * fs,qint64 sectorStart,qint64 sectorEnd,QString partitionPath,PartitionTable::Flags availableFlags,const QString & mountPoint,bool mounted,PartitionTable::Flags activeFlags,State state)45 Partition::Partition(PartitionNode* parent, const Device& device, const PartitionRole& role, FileSystem* fs, qint64 sectorStart, qint64 sectorEnd, QString partitionPath, PartitionTable::Flags availableFlags, const QString& mountPoint, bool mounted, PartitionTable::Flags activeFlags, State state) :
46     PartitionNode(),
47     m_Children(),
48     m_Parent(parent),
49     m_FileSystem(fs),
50     m_Roles(role),
51     m_FirstSector(sectorStart),
52     m_LastSector(sectorEnd),
53     m_DevicePath(device.deviceNode()),
54     m_MountPoint(mountPoint),
55     m_AvailableFlags(availableFlags),
56     m_ActiveFlags(activeFlags),
57     m_IsMounted(mounted),
58     m_State(state)
59 {
60     setPartitionPath(partitionPath);
61     Q_ASSERT(m_Parent);
62     m_SectorSize = device.logicalSize();
63 }
64 
65 /** Destroys a Partition, destroying its children and its FileSystem */
~Partition()66 Partition::~Partition()
67 {
68     // FIXME: Design flaw: Currently, there are two ways a partition node can get children: Either
69     // they're created and inserted as unallocated in PartitionTable (these unallocated then get
70     // "converted" to real, new partitions in the GUI) or they're created and appended in the
71     // backend plugin. There is however no defined way to remove partitions from parents. This might
72     // either cause leaks (a partition is removed from the parent's list of children but never
73     // deleted) or, worse, crashes (a partition is deleted but not removed from the parent's
74     // list of children). As a workaround, always remove a partition from its parent here in the dtor.
75     // This presumably fixes 232092.
76     if (m_Parent)
77         parent()->remove(this);
78     clearChildren();
79     deleteFileSystem();
80 }
81 
82 /** @param other Partition to copy
83 */
Partition(const Partition & other,PartitionNode * parent)84 Partition::Partition(const Partition& other, PartitionNode* parent) :
85     PartitionNode(),
86     m_Children(),
87     m_Parent(other.m_Parent),
88     m_FileSystem(FileSystemFactory::create(other.fileSystem())),
89     m_Roles(other.m_Roles),
90     m_FirstSector(other.m_FirstSector),
91     m_LastSector(other.m_LastSector),
92     m_DevicePath(other.m_DevicePath),
93     m_Label(other.m_Label),
94     m_UUID(other.m_UUID),
95     m_MountPoint(other.m_MountPoint),
96     m_AvailableFlags(other.m_AvailableFlags),
97     m_ActiveFlags(other.m_ActiveFlags),
98     m_IsMounted(other.m_IsMounted),
99     m_SectorSize(other.m_SectorSize),
100     m_State(other.m_State)
101 {
102     if ( parent )
103         m_Parent = parent;
104 
105     setPartitionPath(other.m_PartitionPath);
106     for (const auto &child : other.children()) {
107         Partition* p = new Partition(*child, this);
108         m_Children.append(p);
109     }
110 }
111 
112 /** @param other Partition to assign from */
operator =(const Partition & other)113 Partition& Partition::operator=(const Partition& other)
114 {
115     if (&other == this)
116         return *this;
117 
118     clearChildren();
119 
120     for (const auto &child : other.children()) {
121         Partition* p = new Partition(*child);
122         p->setParent(this);
123         m_Children.append(p);
124     }
125 
126     m_Number = other.m_Number;
127     m_FileSystem = FileSystemFactory::create(other.fileSystem());
128     m_Roles = other.m_Roles;
129     m_FirstSector = other.m_FirstSector;
130     m_LastSector = other.m_LastSector;
131     m_DevicePath = other.m_DevicePath;
132     m_Label = other.m_Label;
133     m_UUID = other.m_UUID;
134     m_PartitionPath = other.m_PartitionPath;
135     m_MountPoint = other.m_MountPoint;
136     m_AvailableFlags = other.m_AvailableFlags;
137     m_ActiveFlags = other.m_ActiveFlags;
138     m_IsMounted = other.m_IsMounted;
139     m_SectorSize = other.m_SectorSize;
140     m_State = other.m_State;
141 
142     return *this;
143 }
144 
operator ==(const Partition & other) const145 bool Partition::operator==(const Partition& other) const
146 {
147     return other.deviceNode() == deviceNode();
148 }
149 
operator !=(const Partition & other) const150 bool Partition::operator!=(const Partition& other) const
151 {
152     return !(other == *this);
153 }
154 
155 /** @return a short descriptive text or, in case the Partition has StateNone, its device node. */
deviceNode() const156 QString Partition::deviceNode() const
157 {
158     if (roles().has(PartitionRole::None) || roles().has(PartitionRole::Unallocated))
159         return xi18nc("@item partition name", "unallocated");
160 
161     if (state() == State::New)
162         return xi18nc("@item partition name", "New Partition");
163 
164     if (state() == State::Restore)
165         return xi18nc("@item partition name", "Restored Partition");
166 
167     if (state() == State::Copy)
168         return xi18nc("@item partition name", "Copy of %1", partitionPath());
169 
170     return partitionPath();
171 }
172 
173 /** @return the sectors used in the Partition's FileSystem or, in case of an extended partition, the sum of used sectors of the Partition's children */
sectorsUsed() const174 qint64 Partition::sectorsUsed() const
175 {
176     // Make sure file system exists. In some cases (due to bugs elsewhere?) file system pointer did not exist, especially for unallocated space.
177     if (m_FileSystem == nullptr)
178         return -1;
179 
180     if (!roles().has(PartitionRole::Extended))
181         return fileSystem().sectorsUsed();
182 
183     qint64 result = 0;
184     for (const auto &p : children())
185         if (!p->roles().has(PartitionRole::Unallocated))
186             result += p->length();
187 
188     return result;
189 }
190 
191 /** @return the minimum number of sectors this Partition must be long */
minimumSectors() const192 qint64 Partition::minimumSectors() const
193 {
194     if (roles().has(PartitionRole::Luks))
195         return ( fileSystem().minCapacity() + (4096 * 512) ) / sectorSize(); // 4096 is the default cryptsetup payload offset
196     return fileSystem().minCapacity() / sectorSize();
197 }
198 
199 /** @return the maximum number of sectors this Partition may be long */
maximumSectors() const200 qint64 Partition::maximumSectors() const
201 {
202     return fileSystem().maxCapacity() / sectorSize();
203 }
204 
205 /** Adjusts the numbers of logical Partitions for an extended Partition.
206 
207     This is required if a logical Partition is deleted or inserted because logicals must be numberd from
208     5 onwards without a gap. So if the user deletes Partition number 7 and there is a number 8, 8 becomes the
209     "new" 7. And since this happens somewhere in the middle of a DeleteOperation, we have to adjust to that so the
210     next Job still finds the Partition it wants to deal with.
211 
212     @param deletedNumber the number of a deleted logical or -1 if none has been deleted
213     @param insertedNumber the number of an inserted logical or -1 if none has been inserted
214 */
adjustLogicalNumbers(qint32 deletedNumber,qint32 insertedNumber) const215 void Partition::adjustLogicalNumbers(qint32 deletedNumber, qint32 insertedNumber) const
216 {
217     if (!roles().has(PartitionRole::Extended))
218         return;
219 
220     for (const auto &p : children()) {
221         QString path = p->partitionPath();
222         path.remove(QRegularExpression(QStringLiteral("(\\d+$)")));
223         if (deletedNumber > 4 && p->number() > deletedNumber)
224             p->setPartitionPath(path + QString::number(p->number() - 1));
225         else if (insertedNumber > 4 && p->number() >= insertedNumber)
226             p->setPartitionPath(path + QString::number(p->number() + 1));
227     }
228 }
229 
230 /** @return the highest sector number an extended Partition can begin at */
maxFirstSector() const231 qint64 Partition::maxFirstSector() const
232 {
233     qint64 rval = -1;
234 
235     for (const auto &child : children())
236         if (!child->roles().has(PartitionRole::Unallocated) && (child->firstSector() < rval || rval == -1))
237             rval = child->firstSector();
238 
239     return rval;
240 }
241 
242 /** @return the lowest sector number an extended Partition can end at */
minLastSector() const243 qint64 Partition::minLastSector() const
244 {
245     qint64 rval = -1;
246 
247     for (const auto &child : children())
248         if (!child->roles().has(PartitionRole::Unallocated) && child->lastSector() > rval)
249             rval = child->lastSector();
250 
251     return rval;
252 }
253 
254 /** @return true if the Partition has children */
hasChildren() const255 bool Partition::hasChildren() const
256 {
257     for (const auto &child : children())
258         if (!child->roles().has(PartitionRole::Unallocated))
259             return true;
260 
261     return false;
262 }
263 
264 /** @return returns the Partition Table which contains this Partition */
partitionTable() const265 const PartitionTable* Partition::partitionTable() const {
266     const PartitionNode *p = this;
267     while (p->parent() != nullptr) {
268         p = p->parent();
269     }
270     return static_cast<const PartitionTable*>(p);
271 }
272 
273 /** Sets an extended Partition to mounted if any of its children are mounted */
checkChildrenMounted()274 void Partition::checkChildrenMounted()
275 {
276     setMounted(isChildMounted());
277 }
278 
279 /** @return true if this Partition can be mounted */
canMount() const280 bool Partition::canMount() const
281 {
282     // cannot mount if already mounted
283     if (isMounted())  {
284         return false;
285     }
286 
287     if (fileSystem().canMount(deviceNode(), mountPoint())) {
288         return true;
289     }
290 
291     return false;
292 }
293 
294 /** @return true if this Partition can be unmounted */
canUnmount() const295 bool Partition::canUnmount() const
296 {
297     return !roles().has(PartitionRole::Extended) && isMounted() && fileSystem().canUnmount(deviceNode());
298 }
299 
setMounted(bool b)300 void Partition::setMounted(bool b) {
301     m_IsMounted = b;
302     if (roles().has(PartitionRole::Luks))
303         static_cast<FS::luks*>(m_FileSystem)->setMounted(b);
304 }
305 
306 /** Tries to mount a Partition.
307     @return true on success
308 */
mount(Report & report)309 bool Partition::mount(Report& report)
310 {
311     if (isMounted())
312         return false;
313 
314     bool success = false;
315 
316     if (fileSystem().canMount(deviceNode(), mountPoint())) {
317         success = fileSystem().mount(report, deviceNode(), mountPoint());
318     }
319 
320     setMounted(success);
321 
322     return success;
323 }
324 
325 /** Tries to unmount a Partition.
326     @return true on success
327 */
unmount(Report & report)328 bool Partition::unmount(Report& report)
329 {
330     if (!isMounted())
331         return false;
332 
333     bool success = false;
334 
335     if (fileSystem().canUnmount(deviceNode())) {
336         success = fileSystem().unmount(report, deviceNode());
337     }
338 
339     const QString canonicalDeviceNode = QFileInfo(deviceNode()).canonicalFilePath();
340     const QList<QStorageInfo> mountedVolumes = QStorageInfo::mountedVolumes();
341     for (const QStorageInfo &storage : mountedVolumes) {
342         if (QFileInfo(QFile::decodeName(storage.device())).canonicalFilePath() == canonicalDeviceNode ) {
343             success = false;
344             break;
345         }
346     }
347 
348     setMounted(!success);
349 
350     return success;
351 }
352 
deleteFileSystem()353 void Partition::deleteFileSystem()
354 {
355     delete m_FileSystem;
356     m_FileSystem = nullptr;
357 }
358 
setPartitionPath(const QString & s)359 void Partition::setPartitionPath(const QString& s)
360 {
361     m_PartitionPath = s;
362     QRegularExpression re(QStringLiteral("(\\d+$)"));
363     QRegularExpressionMatch rePartitionNumber = re.match(partitionPath());
364     if (rePartitionNumber.hasMatch()) {
365         setNumber(rePartitionNumber.captured().toInt());
366         return;
367     }
368     setNumber(-1);
369 }
370 
setFileSystem(FileSystem * fs)371 void Partition::setFileSystem(FileSystem* fs)
372 {
373     m_FileSystem = fs;
374 }
375 
move(qint64 newStartSector)376 void Partition::move(qint64 newStartSector)
377 {
378     const qint64 savedLength = length();
379     setFirstSector(newStartSector);
380     setLastSector(newStartSector + savedLength - 1);
381 }
382 
operator <<(QTextStream & stream,const Partition & p)383 QTextStream& operator<<(QTextStream& stream, const Partition& p)
384 {
385     QStringList flagList;
386 
387     for (const auto &f : PartitionTable::flagList()) {
388         if (p.activeFlags() & f)
389             flagList.append(PartitionTable::flagName(f));
390     }
391 
392     const QString sep(QStringLiteral(";"));
393 
394     // number - start - end - type - roles - label - flags
395     stream << p.number() << sep
396            << p.firstSector() << sep
397            << p.lastSector() << sep
398            << p.fileSystem().name({ QStringLiteral("C") }) << sep
399            << p.roles().toString({ QStringLiteral("C") }) << sep
400            << "\"" << p.fileSystem().label() << QStringLiteral("\"") << sep
401            << "\"" << flagList.join(QStringLiteral(",")) << QStringLiteral("\"")
402            << "\n";
403 
404     return stream;
405 }
406 
407