1 //===========================================
2 //  Lumina-Desktop source code
3 //  Copyright (c) 2016, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "imgDialog.h"
8 #include "ui_imgDialog.h"
9 
10 #include <QMessageBox>
11 
12 #include <LuminaOS.h>
13 #include <LuminaXDG.h>
14 
15 #include <unistd.h>
16 #include <sys/types.h>
17 #include <signal.h>
18 
imgDialog(QWidget * parent)19 imgDialog::imgDialog(QWidget *parent) : QDialog(parent), ui(new Ui::imgDialog()){
20   ui->setupUi(this); //load the designer form
21   QString title = tr("Burn Disk Image to Device");
22   if( getuid()==0){ title.append(" ("+tr("Admin Mode")+")"); }
23   this->setWindowTitle(title);
24   ui->frame_running->setVisible(false);
25   ui->frame_setup->setVisible(true);
26   ddProc = 0;
27   unitdiv = 1;
28   //Setup the signals/slots
29   ui->tool_refresh->setIcon( LXDG::findIcon("view-refresh","drive-removable-media-usb-pendrive") );
30   connect(ui->push_cancel, SIGNAL(clicked()), this, SLOT(cancel()) );
31   connect(ui->push_start, SIGNAL(clicked()), this, SLOT(start_process()) );
32   connect(ui->tool_refresh, SIGNAL(clicked()), this, SLOT(loadDeviceList()) );
33   loadDeviceList(); //do the initial load of the available devices
34   //Setup the possible transfer rate units
35   ui->combo_rate_units->clear();
36   ui->combo_rate_units->addItem(tr("Kilobyte(s)"), "k");
37   ui->combo_rate_units->addItem(tr("Megabyte(s)"), "m");
38   ui->combo_rate_units->addItem(tr("Gigabyte(s)"), "g");
39   ui->combo_rate_units->setCurrentIndex(1); //MB
40   //Setup the Process Timer
41   procTimer = new QTimer(this);
42     procTimer->setInterval(1000); //1 second updates
43   connect(procTimer, SIGNAL(timeout()), this, SLOT(getProcStatus()) );
44   //Determine which type of system this is for the process status signal
45   BSD_signal = LOS::OSName().contains("BSD"); //assume everything else is Linux-like
46 }
47 
~imgDialog()48 imgDialog::~imgDialog(){
49 
50 }
51 
loadIMG(QString filepath)52 void imgDialog::loadIMG(QString filepath){
53   ui->label_iso->setText(filepath.section("/",-1)); //only show the filename
54   ui->label_iso->setWhatsThis(filepath); //save the full path for later
55 }
56 
57 //============================
58 //  PRIVATE SLOTS
59 //============================
start_process()60 void imgDialog::start_process(){
61   //Sanity Checks
62   if( !QFile::exists(ui->combo_devices->currentData().toString()) ){ loadDeviceList(); return; } //USB device no longer available
63   if(!QFile::exists(ui->label_iso->whatsThis()) ){ return; } //IMG file no longer available
64   qDebug() << "Start Process...";
65   //Read the size of the img file
66   QString units = ui->combo_rate_units->currentData().toString();
67   if(units=="k"){ unitdiv = 1024; }
68   else if(units=="m"){ unitdiv = 1024*1024; }
69   else if(units=="g"){ unitdiv = 1024*1024*1024; }
70   qint64 bytes = QFileInfo(ui->label_iso->whatsThis()).size();
71   //qDebug() << "IMG File size:" << bytes;
72   //Set the progressBar maximum
73   ui->progressBar->setRange(0, qRound(bytes/unitdiv) );
74   ui->progressBar->setValue(0);
75   ui->label_dev->setText( ui->combo_devices->currentText() );
76   ui->label_time->setText("0:00");
77   ui->frame_running->setVisible(true);
78   ui->frame_setup->setVisible(false);
79   //qDebug() << "Blocks:" << ui->progressBar->maximum();
80   //Initialize the process
81   if(ddProc==0){
82     ddProc = new QProcess(this);
83     connect(ddProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(procFinished()) );
84     connect(ddProc, SIGNAL(readyRead()), this, SLOT(procInfoAvailable()) );
85     ddProc->setProcessChannelMode(QProcess::MergedChannels);
86   }
87   //Generate the command
88   QString prog; QStringList args;
89   //if( ::getuid()!=0){ prog = "qsudo";  args<<"dd"; }
90   //else{
91     prog = "dd";
92   //}
93    args << "if="+ui->label_iso->whatsThis();
94    args << "of="+ui->combo_devices->currentData().toString();
95    args << "bs="+QString::number(ui->spin_rate_num->value())+units;
96    if(ui->check_sync->isChecked()){ args << "conv=sync"; }
97   //Start the process
98   startTime = QDateTime::currentDateTime();
99   ddProc->start(prog, args);
100   //Start the timer to watch for updates
101   procTimer->start();
102   ui->push_start->setEnabled(false);
103 }
104 
cancel()105 void imgDialog::cancel(){
106   if(ddProc==0 || ddProc->state()==QProcess::NotRunning){
107     this->close();
108   }else{
109     //Prompt if the transfer should be cancelled
110     if(QMessageBox::Yes == QMessageBox::question(this, tr("Cancel Image Burn?"), tr("Do you wish to stop the current disk image burn process?")+"\n\n"+tr("Warning: This will leave the USB device in an inconsistent state")) ){
111       ddProc->kill();
112     }
113   }
114 }
115 
loadDeviceList()116 void imgDialog::loadDeviceList(){
117   ui->combo_devices->clear();
118   //Probe the system for USB devices
119   QDir devDir("/dev");
120   QString filter = (BSD_signal) ? "da*" : "sd*";
121   QStringList usb = devDir.entryList(QStringList() << filter, QDir::System, QDir::Name);
122   //Filter out any devices which are currently mounted/used
123 
124   //Create the list
125   for(int i=0; i<usb.length(); i++){
126     ui->combo_devices->addItem(usb[i], devDir.absoluteFilePath(usb[i]));
127   }
128 }
129 
getProcStatus()130 void imgDialog::getProcStatus(){
131   if(ddProc==0 || ddProc->state()!=QProcess::Running ){ return; }
132   QStringList pidlist = LUtils::getCmdOutput("pgrep -S -f \"dd if="+ui->label_iso->whatsThis()+"\"");
133   if(pidlist.isEmpty()){ return; }
134   int pid = pidlist.first().simplified().toInt(); //just use the first pid - the pgrep should be detailed enough to only match one
135   //qDebug() << "Sending signal to show status on PID:" << pid;
136 #ifndef __linux__
137   ::kill(pid, SIGINFO); //On BSD systems, the INFO signal is used to poke dd for status updates
138 #else
139   //LINUX systems do not even have a SIGINFO defined - need to block this off with defines...
140   ::kill(pid, SIGUSR1);  //On linux systems, the USR1 signal is used to poke dd for status updates
141 #endif
142   //Now update the elapsed time on the UI
143     int elapsed = startTime.secsTo( QDateTime::currentDateTime() );
144     int min = elapsed/60;
145     int secs = elapsed%60;
146     ui->label_time->setText( QString::number(min)+":"+ (secs < 10 ? "0" : "")+QString::number(secs) );
147 }
148 
procInfoAvailable()149 void imgDialog::procInfoAvailable(){
150   lastmsg = ddProc->readAll();
151   if(lastmsg.endsWith("\n")){ lastmsg.chop(1); }
152   //qDebug() << "Got Process Info:" << lastmsg;
153   //Now look for the " bytes transferred" line
154   QStringList records = lastmsg.split("\n").filter(" bytes transferred ");
155   if(!records.isEmpty()){
156     //Update the progress bar
157     //qDebug() << "Got status update:" << records.last();
158     ui->progressBar->setValue( qRound(records.last().section(" bytes",0,0).toDouble()/unitdiv) );
159   }
160 }
161 
procFinished()162 void imgDialog::procFinished(){
163   qDebug() << "Process Finished:" << ddProc->exitCode();
164   procTimer->stop();
165   ui->frame_running->setVisible(false);
166   ui->frame_setup->setVisible(true);
167   if(ddProc->exitStatus()==QProcess::NormalExit){
168     if(ddProc->exitCode() !=0 ){
169       if(lastmsg.contains("permission denied", Qt::CaseInsensitive) && LUtils::isValidBinary("qsudo") ){
170         if(QMessageBox::Yes == QMessageBox::question(this, tr("Administrator Permissions Needed"), tr("This operation requires administrator priviledges.")+"\n\n"+tr("Would you like to enable these priviledges?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ){
171           QProcess::startDetached("qsudo", QStringList() << "lumina-archiver" << "--burn-img" << ui->label_iso->whatsThis());
172           exit(0);
173         }
174       }else{
175         QMessageBox::warning(this, tr("ERROR"), tr("The process could not be completed:")+"\n\n"+lastmsg);
176       }
177     }else{
178       QMessageBox::information(this, tr("SUCCESS"), tr("The image was successfully burned to the device") );
179       this->close();
180     }
181   }
182 }
183