1 /*
2     SPDX-FileCopyrightText: 2010 Volker Lanz <vl@fidra.de>
3     SPDX-FileCopyrightText: 2014-2018 Andrius Štikonas <andrius@stikonas.eu>
4     SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org>
5     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
6     SPDX-FileCopyrightText: 2019 Yuri Chornoivan <yurchor@ukr.net>
7 
8     SPDX-License-Identifier: GPL-3.0-or-later
9 */
10 
11 #include "core/partitionalignment.h"
12 
13 #include "core/partition.h"
14 #include "core/partitiontable.h"
15 #include "core/device.h"
16 #include "core/diskdevice.h"
17 
18 #include "fs/filesystem.h"
19 
20 #include "util/globallog.h"
21 
22 #include <KLocalizedString>
23 
24 int PartitionAlignment::s_sectorAlignment = 2048;
25 
firstDelta(const Device & d,const Partition & p,qint64 s)26 qint64 PartitionAlignment::firstDelta(const Device& d, const Partition& p, qint64 s)
27 {
28     if (d.partitionTable()->type() == PartitionTable::msdos) {
29         const DiskDevice& diskDevice = dynamic_cast<const DiskDevice&>(d);
30         if (p.roles().has(PartitionRole::Logical) && s == 2 * diskDevice.sectorsPerTrack())
31             return (s - (2 * diskDevice.sectorsPerTrack())) % sectorAlignment(d);
32 
33         if (p.roles().has(PartitionRole::Logical) || s == diskDevice.sectorsPerTrack())
34             return (s - diskDevice.sectorsPerTrack()) % sectorAlignment(d);
35     }
36 
37     return s % sectorAlignment(d);
38 }
39 
lastDelta(const Device & d,const Partition &,qint64 s)40 qint64 PartitionAlignment::lastDelta(const Device& d, const Partition&, qint64 s)
41 {
42     return (s + 1) % sectorAlignment(d);
43 }
44 
isLengthAligned(const Device & d,const Partition & p)45 bool PartitionAlignment::isLengthAligned(const Device& d, const Partition& p)
46 {
47     if (d.partitionTable()->type() == PartitionTable::msdos) {
48         const DiskDevice& diskDevice = dynamic_cast<const DiskDevice&>(d);
49         if (p.roles().has(PartitionRole::Logical) && p.firstSector() == 2 * diskDevice.sectorsPerTrack())
50             return (p.length() + (2 * diskDevice.sectorsPerTrack())) % sectorAlignment(d) == 0;
51 
52         if (p.roles().has(PartitionRole::Logical) || p.firstSector() == diskDevice.sectorsPerTrack())
53             return (p.length() + diskDevice.sectorsPerTrack()) % sectorAlignment(d) == 0;
54     }
55 
56     return p.length() % sectorAlignment(d) == 0;
57 }
58 
59 /** Checks if the Partition is properly aligned to the PartitionTable's alignment requirements.
60 
61     Will print warning messages to GlobalLog if the Partition's first sector is not aligned and
62     another one if the last sector is not aligned. This can be suppressed with setting the @p quiet to
63     true.
64 
65     @see alignPartition(), canAlignToSector()
66 
67     @param d device the partition is on
68     @param p the partition to check
69     @param quiet if true, will not print warning
70     @return true if properly aligned
71 */
isAligned(const Device & d,const Partition & p,bool quiet)72 bool PartitionAlignment::isAligned(const Device& d, const Partition& p, bool quiet)
73 {
74     return isAligned(d, p, p.firstSector(), p.lastSector(), quiet);
75 }
76 
isAligned(const Device & d,const Partition & p,qint64 newFirst,qint64 newLast,bool quiet)77 bool PartitionAlignment::isAligned(const Device& d, const Partition& p, qint64 newFirst, qint64 newLast, bool quiet)
78 {
79     if (firstDelta(d, p, newFirst) && !quiet)
80         Log(Log::Level::warning) << xi18nc("@info:status", "Partition <filename>%1</filename> is not properly aligned (first sector: %2, modulo: %3).", p.deviceNode(), newFirst, firstDelta(d, p, newFirst));
81 
82     if (lastDelta(d, p, newLast) && !quiet)
83         Log(Log::Level::warning) << xi18nc("@info:status", "Partition <filename>%1</filename> is not properly aligned (last sector: %2, modulo: %3).", p.deviceNode(), newLast, lastDelta(d, p, newLast));
84 
85     return firstDelta(d, p, newFirst) == 0 && lastDelta(d, p, newLast) == 0;
86 }
87 
88 /** @return the sector size to align the partition start and end to
89 */
sectorAlignment(const Device & d)90 qint64 PartitionAlignment::sectorAlignment(const Device& d)
91 {
92     Q_UNUSED(d)
93     return s_sectorAlignment;
94 }
95 
setSectorAlignment(int sectorAlignment)96 void PartitionAlignment::setSectorAlignment(int sectorAlignment)
97 {
98     s_sectorAlignment = sectorAlignment;
99 }
100 
alignedFirstSector(const Device & d,const Partition & p,qint64 s,qint64 min_first,qint64 max_first,qint64 min_length,qint64 max_length)101 qint64 PartitionAlignment::alignedFirstSector(const Device& d, const Partition& p, qint64 s, qint64 min_first, qint64 max_first, qint64 min_length, qint64 max_length)
102 {
103     if (firstDelta(d, p, s) == 0)
104         return s;
105 
106     /** @todo Don't assume we always want to align to the front.
107         Always trying to align to the front solves the problem that a partition does
108         get too small to take another one that's copied to it, but it introduces
109         a new bug: The user might create a partition aligned at the end of a device,
110         extended partition or at the start of the next one, but we align to the back
111         and leave some space in between.
112     */
113     // We always want to make the partition larger, not smaller. Making it smaller
114     // might, in case it's a partition that another is being copied to, mean the partition
115     // ends up too small. So try to move the start to the front first.
116     s = s - firstDelta(d, p, s);
117 
118     while (s < d.partitionTable()->firstUsable() || s < min_first || (max_length > -1 && p.lastSector() - s + 1 > max_length))
119         s += sectorAlignment(d);
120 
121     while (s > d.partitionTable()->lastUsable() || (max_first > -1 && s > max_first) || p.lastSector() - s + 1 < min_length)
122         s -= sectorAlignment(d);
123 
124     return s;
125 }
126 
alignedLastSector(const Device & d,const Partition & p,qint64 s,qint64 min_last,qint64 max_last,qint64 min_length,qint64 max_length,qint64 original_length,bool original_aligned)127 qint64 PartitionAlignment::alignedLastSector(const Device& d, const Partition& p, qint64 s, qint64 min_last, qint64 max_last, qint64 min_length, qint64 max_length, qint64 original_length, bool original_aligned)
128 {
129     if (lastDelta(d, p, s) == 0)
130         return s;
131 
132     s = s + sectorAlignment(d) - lastDelta(d, p, s);
133 
134     // if we can retain the partition length exactly by aligning to the front, do that
135     if (original_aligned && p.length() - original_length == lastDelta(d, p, s))
136         s -= sectorAlignment(d);
137 
138     while (s < d.partitionTable()->firstUsable() || s < min_last || s - p.firstSector() + 1 < min_length)
139         s += sectorAlignment(d);
140 
141     while (s > d.partitionTable()->lastUsable() || (max_last > -1 && s > max_last) || (max_length > -1 && s - p.firstSector() + 1 > max_length))
142         s -= sectorAlignment(d);
143 
144     return s;
145 }
146