1 /*
2     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
3     SPDX-FileCopyrightText: 2012-2019 Andrius Štikonas <andrius@stikonas.eu>
4     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
5 
6     SPDX-License-Identifier: GPL-3.0-or-later
7 */
8 
9 #include "ops/copyoperation.h"
10 
11 #include "core/partition.h"
12 #include "core/device.h"
13 
14 #include "jobs/createpartitionjob.h"
15 #include "jobs/deletepartitionjob.h"
16 #include "jobs/checkfilesystemjob.h"
17 #include "jobs/copyfilesystemjob.h"
18 #include "jobs/resizefilesystemjob.h"
19 
20 #include "fs/filesystemfactory.h"
21 
22 #include "util/capacity.h"
23 #include "util/report.h"
24 
25 #include <QDebug>
26 #include <QString>
27 
28 #include <KLocalizedString>
29 
30 /** Creates a new CopyOperation.
31     @param targetdevice the Device to copy the Partition to
32     @param copiedpartition pointer to the new Partition object on the target Device. May not be nullptr.
33     @param sourcedevice the Device where to copy from
34     @param sourcepartition pointer to the Partition to copy from. May not be nullptr.
35 */
CopyOperation(Device & targetdevice,Partition * copiedpartition,Device & sourcedevice,Partition * sourcepartition)36 CopyOperation::CopyOperation(Device& targetdevice, Partition* copiedpartition, Device& sourcedevice, Partition* sourcepartition) :
37     Operation(),
38     m_TargetDevice(targetdevice),
39     m_CopiedPartition(copiedpartition),
40     m_SourceDevice(sourcedevice),
41     m_SourcePartition(sourcepartition),
42     m_OverwrittenPartition(nullptr),
43     m_MustDeleteOverwritten(false),
44     m_CheckSourceJob(nullptr),
45     m_CreatePartitionJob(nullptr),
46     m_CopyFSJob(nullptr),
47     m_CheckTargetJob(nullptr),
48     m_MaximizeJob(nullptr),
49     m_Description(updateDescription())
50 {
51     Q_ASSERT(targetDevice().partitionTable());
52 
53     Partition* dest = targetDevice().partitionTable()->findPartitionBySector(copiedPartition().firstSector(), PartitionRole(PartitionRole::Primary | PartitionRole::Logical | PartitionRole::Unallocated));
54 
55     if (dest == nullptr)
56         qWarning() << "destination partition not found at sector " << copiedPartition().firstSector();
57 
58     Q_ASSERT(dest);
59 
60     if (dest && !dest->roles().has(PartitionRole::Unallocated)) {
61         copiedPartition().setLastSector(dest->lastSector());
62         setOverwrittenPartition(dest);
63     }
64 
65     addJob(m_CheckSourceJob = new CheckFileSystemJob(sourcePartition()));
66 
67     if (overwrittenPartition() == nullptr)
68         addJob(m_CreatePartitionJob = new CreatePartitionJob(targetDevice(), copiedPartition()));
69 
70     addJob(m_CopyFSJob = new CopyFileSystemJob(targetDevice(), copiedPartition(), sourceDevice(), sourcePartition()));
71     addJob(m_CheckTargetJob = new CheckFileSystemJob(copiedPartition()));
72     addJob(m_MaximizeJob = new ResizeFileSystemJob(targetDevice(), copiedPartition()));
73 }
74 
~CopyOperation()75 CopyOperation::~CopyOperation()
76 {
77     if (status() == StatusPending)
78         delete m_CopiedPartition;
79 
80     if (status() == StatusFinishedSuccess || status() == StatusFinishedWarning || status() == StatusError)
81         cleanupOverwrittenPartition();
82 }
83 
targets(const Device & d) const84 bool CopyOperation::targets(const Device& d) const
85 {
86     return d == targetDevice();
87 }
88 
targets(const Partition & p) const89 bool CopyOperation::targets(const Partition& p) const
90 {
91     return p == copiedPartition();
92 }
93 
preview()94 void CopyOperation::preview()
95 {
96     if (overwrittenPartition())
97         removePreviewPartition(targetDevice(), *overwrittenPartition());
98 
99     insertPreviewPartition(targetDevice(), copiedPartition());
100 }
101 
undo()102 void CopyOperation::undo()
103 {
104     removePreviewPartition(targetDevice(), copiedPartition());
105 
106     if (overwrittenPartition())
107         insertPreviewPartition(targetDevice(), *overwrittenPartition());
108 }
109 
execute(Report & parent)110 bool CopyOperation::execute(Report& parent)
111 {
112     bool rval = false;
113     bool warning = false;
114 
115     Report* report = parent.newChild(description());
116 
117     // check the source first
118     if ((rval = checkSourceJob()->run(*report))) {
119         // At this point, if the target partition is to be created and not overwritten, it
120         // will still have the wrong device path (the one of the source device). We need
121         // to adjust that before we're creating it.
122         copiedPartition().setDevicePath(targetDevice().deviceNode());
123 
124         // either we have no partition to create (because we're overwriting) or creating
125         // must be successful
126         if (!createPartitionJob() || (rval = createPartitionJob()->run(*report))) {
127             // set the state of the target partition from StateCopy to StateNone or checking
128             // it will fail (because its deviceNode() will still be "Copy of sdXn"). This is
129             // only required for overwritten partitions, but doesn't hurt in any case.
130             copiedPartition().setState(Partition::State::None);
131 
132             // if we have overwritten a partition, reset device path and number
133             if (overwrittenPartition()) {
134                 copiedPartition().setDevicePath(overwrittenPartition()->devicePath());
135                 copiedPartition().setPartitionPath(overwrittenPartition()->partitionPath());
136             }
137 
138             // now run the copy job itself
139             if ((rval = copyFSJob()->run(*report))) {
140                 // and if the copy job succeeded, check the target
141                 if ((rval = checkTargetJob()->run(*report))) {
142                     // ok, everything went well
143                     rval = true;
144 
145                     // if maximizing doesn't work, just warn the user, don't fail
146                     if (!maximizeJob()->run(*report)) {
147                         report->line() << xi18nc("@info:status", "<warning>Maximizing file system on target partition <filename>%1</filename> to the size of the partition failed.</warning>", copiedPartition().deviceNode());
148                         warning = true;
149                     }
150                 } else
151                     report->line() << xi18nc("@info:status", "Checking target partition <filename>%1</filename> after copy failed.", copiedPartition().deviceNode());
152             } else {
153                 if (createPartitionJob()) {
154                     DeletePartitionJob deleteJob(targetDevice(), copiedPartition());
155                     deleteJob.run(*report);
156                 }
157 
158                 report->line() << xi18nc("@info:status", "Copying source to target partition failed.");
159             }
160         } else
161             report->line() << xi18nc("@info:status", "Creating target partition for copying failed.");
162     } else
163         report->line() << xi18nc("@info:status", "Checking source partition <filename>%1</filename> failed.", sourcePartition().deviceNode());
164 
165     if (rval)
166         setStatus(warning ? StatusFinishedWarning : StatusFinishedSuccess);
167     else
168         setStatus(StatusError);
169 
170     report->setStatus(xi18nc("@info:status (success, error, warning...) of operation", "%1: %2", description(), statusText()));
171 
172     return rval;
173 }
174 
updateDescription() const175 QString CopyOperation::updateDescription() const
176 {
177     if (overwrittenPartition()) {
178         if (copiedPartition().length() == overwrittenPartition()->length())
179             return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to <filename>%4</filename> (%5, %6)",
180                           sourcePartition().deviceNode(),
181                           Capacity::formatByteSize(sourcePartition().capacity()),
182                           sourcePartition().fileSystem().name(),
183                           overwrittenPartition()->deviceNode(),
184                           Capacity::formatByteSize(overwrittenPartition()->capacity()),
185                           overwrittenPartition()->fileSystem().name()
186                          );
187 
188         return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to <filename>%4</filename> (%5, %6) and grow it to %7",
189                       sourcePartition().deviceNode(),
190                       Capacity::formatByteSize(sourcePartition().capacity()),
191                       sourcePartition().fileSystem().name(),
192                       overwrittenPartition()->deviceNode(),
193                       Capacity::formatByteSize(overwrittenPartition()->capacity()),
194                       overwrittenPartition()->fileSystem().name(),
195                       Capacity::formatByteSize(copiedPartition().capacity())
196                      );
197     }
198 
199     if (copiedPartition().length() == sourcePartition().length())
200         return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to unallocated space (starting at %4) on <filename>%5</filename>",
201                       sourcePartition().deviceNode(),
202                       Capacity::formatByteSize(sourcePartition().capacity()),
203                       sourcePartition().fileSystem().name(),
204                       Capacity::formatByteSize(copiedPartition().firstSector() * targetDevice().logicalSize()),
205                       targetDevice().deviceNode()
206                      );
207 
208     return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to unallocated space (starting at %4) on <filename>%5</filename> and grow it to %6",
209                   sourcePartition().deviceNode(),
210                   Capacity::formatByteSize(sourcePartition().capacity()),
211                   sourcePartition().fileSystem().name(),
212                   Capacity::formatByteSize(copiedPartition().firstSector() * targetDevice().logicalSize()),
213                   targetDevice().deviceNode(),
214                   Capacity::formatByteSize(copiedPartition().capacity())
215                  );
216 }
217 
setOverwrittenPartition(Partition * p)218 void CopyOperation::setOverwrittenPartition(Partition* p)
219 {
220     // this code is also in RestoreOperation.
221 
222     cleanupOverwrittenPartition();
223     m_OverwrittenPartition = p;
224 
225     // If the overwritten partition has no other operation that owns it (e.g., an OperationNew or
226     // an OperationRestore), we're the new owner. So remember that, because after the operations all
227     // have executed and we're asked to clean up after ourselves, the state of the overwritten partition
228     // might have changed: If it was a new one and the NewOperation has successfully run, the state will
229     // then be StateNone.
230     m_MustDeleteOverwritten = (p && p->state() == Partition::State::None);
231 }
232 
cleanupOverwrittenPartition()233 void CopyOperation::cleanupOverwrittenPartition()
234 {
235     if (mustDeleteOverwritten()) {
236         delete overwrittenPartition();
237         m_OverwrittenPartition = nullptr;
238     }
239 }
240 
241 /** Creates a new copied Partition.
242     @param target the target Partition to copy to (may be unallocated)
243     @param source the source Partition to copy
244     @return pointer to the newly created Partition object
245 */
createCopy(const Partition & target,const Partition & source)246 Partition* CopyOperation::createCopy(const Partition& target, const Partition& source)
247 {
248     Partition* p = target.roles().has(PartitionRole::Unallocated) ? new Partition(source) : new Partition(target);
249 
250     p->setDevicePath(source.devicePath());
251     p->setPartitionPath(source.partitionPath());
252     p->setState(Partition::State::Copy);
253 
254     p->deleteFileSystem();
255     p->setFileSystem(FileSystemFactory::create(source.fileSystem()));
256 
257     p->fileSystem().setFirstSector(p->firstSector());
258     p->fileSystem().setLastSector(p->lastSector());
259 
260     p->setFlags(PartitionTable::Flag::None);
261 
262     return p;
263 }
264 
265 /** Can a Partition be copied?
266     @param p the Partition in question, may be nullptr.
267     @return true if @p p can be copied.
268  */
canCopy(const Partition * p)269 bool CopyOperation::canCopy(const Partition* p)
270 {
271     if (p == nullptr)
272         return false;
273 
274     if (p->state() == Partition::State::New && p->roles().has(PartitionRole::Luks))
275         return false;
276 
277     if (p->isMounted())
278         return false;
279 
280     // FIXME: Does not work well enough yet
281     if (p->roles().has(PartitionRole::Lvm_Lv))
282         return false;
283 
284     // Normally, copying partitions that have not been written to disk yet should
285     // be forbidden here. The operation stack, however, will take care of these
286     // problematic cases when pushing the CopyOperation onto the stack.
287 
288     return p->fileSystem().supportCopy() != FileSystem::cmdSupportNone;
289 }
290 
291 /** Can a Partition be pasted on another one?
292     @param p the Partition to be pasted to, may be nullptr
293     @param source the Partition to be pasted, may be nullptr
294     @return true if @p source can be pasted on @p p
295  */
canPaste(const Partition * p,const Partition * source)296 bool CopyOperation::canPaste(const Partition* p, const Partition* source)
297 {
298     if (p == nullptr || source == nullptr)
299         return false;
300 
301     if (p->isMounted())
302         return false;
303 
304     if (p->roles().has(PartitionRole::Extended))
305         return false;
306 
307     if (p->roles().has(PartitionRole::Lvm_Lv))
308         return false;
309 
310     if (p == source)
311         return false;
312 
313     if (source->length() > p->length())
314         return false;
315 
316     if (!p->roles().has(PartitionRole::Unallocated) && p->capacity() > source->fileSystem().maxCapacity())
317         return false;
318 
319     return true;
320 }
321