Warning, file /utilities/isoimagewriter/isoimagewriter/imagewriter.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2016 ROSA
0003     SPDX-License-Identifier: GPL-3.0-or-later
0004 */
0005 
0006 ////////////////////////////////////////////////////////////////////////////////
0007 // Implementation of ImageWriter
0008 
0009 #include "imagewriter.h"
0010 
0011 #include <KLocalizedString>
0012 
0013 #if defined(USE_KAUTH)
0014 #include <kauth_version.h>
0015 #if KAUTH_VERSION >= QT_VERSION_CHECK(5, 92, 0)
0016 #include <KAuth/ActionReply>
0017 #include <KAuth/HelperSupport>
0018 #else
0019 #include <KAuth>
0020 #endif
0021 #endif
0022 
0023 #include <QFile>
0024 #include <KCompressionDevice>
0025 
0026 #include "common.h"
0027 #include "physicaldevice.h"
0028 
0029 ImageWriter::ImageWriter(const QString& ImageFile, UsbDevice* Device, QObject *parent) :
0030     QObject(parent),
0031     m_Device(Device),
0032     m_ImageFile(ImageFile),
0033     m_CancelWriting(false)
0034 {
0035 }
0036 
0037 // The main method that writes the image
0038 void ImageWriter::writeImage()
0039 {
0040     qDebug() << "XX writeImage()";
0041     const qint64 TRANSFER_BLOCK_SIZE = 1024 * 1024;
0042     void* buffer = NULL;
0043 
0044     bool isError = false;
0045     bool cancelRequested = false;
0046     bool zeroing = (m_ImageFile == "");
0047 
0048     // Using try-catch for processing errors
0049     // Invalid values are used for indication non-initialized objects;
0050     // after the try-catch block all the initialized objects are freed
0051     try
0052     {
0053 #if defined(Q_OS_WIN32)
0054         // Using VirtualAlloc so that the buffer was properly aligned (required for
0055         // direct access to devices and for unbuffered reading/writing)
0056         buffer = VirtualAlloc(NULL, TRANSFER_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
0057         if (buffer == NULL)
0058             throw formatErrorMessageFromCode(i18n("Failed to allocate memory for buffer:"));
0059 #elif defined(Q_OS_LINUX) || defined(Q_OS_MAC)
0060         buffer = malloc(TRANSFER_BLOCK_SIZE);
0061         if (buffer == NULL)
0062             throw i18n("Failed to allocate memory for buffer.");
0063 #endif
0064 
0065         QFile imageFile;
0066         if (zeroing)
0067         {
0068             // Prepare zero-filled buffer
0069             memset(buffer, 0, TRANSFER_BLOCK_SIZE);
0070         }
0071         else
0072         {
0073             // Open the source image file for reading
0074             imageFile.setFileName(m_ImageFile);
0075             if (!imageFile.open(QIODevice::ReadOnly))
0076                 throw i18n("Failed to open the image file: %1", imageFile.errorString());
0077         }
0078 
0079         QIODevice* device;
0080         if (imageFile.fileName().endsWith(".gz")) {
0081             device = new KCompressionDevice(&imageFile, true, KCompressionDevice::GZip);
0082             if (!device->open(QIODevice::ReadOnly)) {
0083                 throw i18n("Failed to open compression device: %1", device->errorString());
0084             }
0085         } else if (imageFile.fileName().endsWith(".xz")) {
0086             device = new KCompressionDevice(&imageFile, true, KCompressionDevice::Xz);
0087             if (!device->open(QIODevice::ReadOnly)) {
0088                 throw i18n("Failed to open compression device: %1", device->errorString());
0089             }
0090         } else if (imageFile.fileName().endsWith(".zstd")) {
0091             device = new KCompressionDevice(&imageFile, true, KCompressionDevice::Zstd);
0092             if (!device->open(QIODevice::ReadOnly)) {
0093                 throw i18n("Failed to open compression device: %1", device->errorString());
0094             }
0095         } else {
0096             device = &imageFile;
0097         }
0098 
0099         // Unmount volumes that belong to the selected target device
0100         // TODO: Check first if they are used and show warning
0101         // (problem: have to show request in the GUI thread and return reply back here)
0102         QStringList errMessages;
0103 
0104 #if defined(Q_OS_WIN32)
0105         for (int i = 0; i < m_Device->m_Volumes.size(); ++i)
0106         {
0107             DWORD bret;
0108             HANDLE volume = CreateFile(
0109                 reinterpret_cast<const wchar_t*>(("\\\\.\\" + m_Device->m_Volumes[i]).utf16()),
0110                 GENERIC_READ | GENERIC_WRITE,
0111                 FILE_SHARE_READ | FILE_SHARE_WRITE,
0112                 NULL,
0113                 OPEN_EXISTING,
0114                 0,
0115                 NULL
0116             );
0117             if (volume == INVALID_HANDLE_VALUE)
0118             {
0119                 errMessages << formatErrorMessageFromCode(i18n("Failed to open the drive %1", m_Device->m_Volumes[i]));
0120                 continue;
0121             }
0122             // Trying to lock the volume but ignore if we failed (such call seems to be required for
0123             // dismounting the volume on WinXP)
0124             DeviceIoControl(volume, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bret, NULL);
0125             if (!DeviceIoControl(volume, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bret, NULL))
0126                 errMessages << formatErrorMessageFromCode(i18n("Failed to unmount the drive %1", m_Device->m_Volumes[i]));
0127             CloseHandle(volume);
0128             volume = INVALID_HANDLE_VALUE;
0129         }
0130 #elif defined(Q_OS_MAC)
0131         struct statfs* mntEntries = NULL;
0132         int mntEntriesNum = getmntinfo(&mntEntries, MNT_WAIT);
0133         for (int i = 0; i < mntEntriesNum; ++i)
0134         {
0135             for (int j = 0; j < m_Device->m_Volumes.size(); ++j)
0136             {
0137                 // Check that the mount point is either our target device itself or a partition on it
0138                 if ((mntEntries[i].f_mntfromname == m_Device->m_Volumes[j]) ||
0139                     QString(mntEntries[i].f_mntfromname).startsWith(m_Device->m_Volumes[j] + 's'))
0140                 {
0141                     // Mount point is the selected device or one of its partitions - try to unmount it
0142                     if (unmount(mntEntries[i].f_mntonname, MNT_FORCE) != 0)
0143                         errMessages << i18n("Failed to unmount the volume %1\n%2", m_Device->m_Volumes[i], strerror(errno));
0144                 }
0145             }
0146         }
0147 #endif
0148         if (errMessages.size() > 0)
0149             throw errMessages.join("\n\n");
0150 
0151         // Open the target USB device for writing and lock it
0152         PhysicalDevice deviceFile(m_Device->m_PhysicalDevice);
0153         if (!deviceFile.open())
0154             throw i18n("Failed to open the target device:\n%1", deviceFile.errorString());
0155 
0156         qint64 readBytes;
0157         qint64 writtenBytes;
0158         // Start reading/writing cycle
0159         for (;;)
0160         {
0161             qDebug() << "For Loop3";
0162 #if defined(USE_KAUTH)
0163             if (KAuth::HelperSupport::isStopped()) {
0164                 qDebug() << "isStopped";
0165             } else {
0166                 qDebug() << "not isStopped";
0167             }
0168 #endif
0169             if (zeroing)
0170             {
0171                 readBytes = TRANSFER_BLOCK_SIZE;
0172             }
0173             else
0174             {
0175                 if ((readBytes = device->read(static_cast<char*>(buffer), TRANSFER_BLOCK_SIZE)) <= 0)
0176                     break;
0177             }
0178             // Align the number of bytes to the sector size
0179             readBytes = alignNumber(readBytes, (qint64)m_Device->m_SectorSize);
0180             writtenBytes = deviceFile.write(static_cast<char*>(buffer), readBytes);
0181             if (writtenBytes < 0)
0182                 throw i18n("Failed to write to the device:\n%1", deviceFile.errorString());
0183             if (writtenBytes != readBytes)
0184                 throw i18n("The last block was not fully written (%1 of %2 bytes)!\nAborting.", writtenBytes, readBytes);
0185 
0186 #if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
0187             // In Linux/MacOS the USB device is opened with buffering. Using forced sync to validate progress bar.
0188             // For unknown reason, deviceFile.flush() does not work as intended here.
0189             fsync(deviceFile.handle());
0190 #endif
0191             const int percent = (100 * imageFile.pos()) / imageFile.size();
0192             // Inform the GUI thread that next block was written
0193             // TODO: Make sure that when TRANSFER_BLOCK_SIZE is not a multiple of DEFAULT_UNIT
0194             // this still works or at least fails compilation
0195             emit progressChanged(percent);
0196 
0197 #if defined(USE_KAUTH)
0198             KAuth::HelperSupport::progressStep(percent);
0199 #endif
0200 
0201             // Check for the cancel request (using temporary variable to avoid multiple unlock calls in the code)
0202             m_Mutex.lock();
0203 #if defined(USE_KAUTH)
0204             cancelRequested = KAuth::HelperSupport::isStopped();
0205 #else
0206             cancelRequested = m_CancelWriting;
0207 #endif
0208             m_Mutex.unlock();
0209 
0210             if (cancelRequested)
0211             {
0212 #if defined(USE_KAUTH)
0213                 QVariantMap progressArgs;
0214                 progressArgs[QStringLiteral("cancel")] = true;
0215                 KAuth::HelperSupport::progressStep(progressArgs);
0216 #endif
0217 
0218                 qDebug() << "cancelRequested";
0219                 // The cancel request was issued
0220                 emit cancelled();
0221                 break;
0222             }
0223             if (zeroing)
0224             {
0225                 // In zeroing mode only write 1 block - 1 MB is enough to clear both MBR and GPT
0226                 break;
0227             }
0228         }
0229         if (!zeroing)
0230         {
0231             if (readBytes < 0) {
0232                 throw i18n("Failed to read the image file:\n%1", device->errorString());
0233             }
0234             imageFile.close();
0235         }
0236         deviceFile.close();
0237     }
0238     catch (QString msg)
0239     {
0240         // Something went wrong :-(
0241 #if defined(USE_KAUTH)
0242         QVariantMap args;
0243         args[QStringLiteral("error")] = msg;
0244         KAuth::HelperSupport::progressStep(args);
0245 #endif
0246 
0247         emit error(msg);
0248         isError = true;
0249     }
0250 
0251     if (buffer != NULL)
0252 #if defined(Q_OS_WIN32)
0253         VirtualFree(buffer, TRANSFER_BLOCK_SIZE, MEM_DECOMMIT | MEM_RELEASE);
0254 #elif defined(Q_OS_LINUX) || defined(Q_OS_MAC)
0255         free(buffer);
0256 #endif
0257 
0258     // If no errors occurred and user did not stop the operation, it means everything went fine
0259     if (!isError && !cancelRequested) {
0260         QString message = i18n("The operation completed successfully.") +
0261             "<br><br>" +
0262             (zeroing ? i18n("Now you need to format your device.") : i18n("To be able to store data on this device again, please, use the button \"Wipe USB Disk\"."));
0263 
0264 #if defined(USE_KAUTH)
0265         QVariantMap args;
0266         args[QStringLiteral("success")] = message;
0267         KAuth::HelperSupport::progressStep(args);
0268 #endif
0269 
0270         emit success(message);
0271     }
0272 
0273     // In any case the operation is finished
0274     emit finished();
0275 }
0276 
0277 // Implements reaction to the cancel request from user
0278 void ImageWriter::cancelWriting()
0279 {
0280     m_Mutex.lock();
0281     m_CancelWriting = true;
0282     m_Mutex.unlock();
0283 }