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