source: src-qt4/life-preserver/LPBackend.cpp @ 284b216

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2
Last change on this file since 284b216 was 284b216, checked in by Ken Moore <ken@…>, 12 months ago

Add a new class to life-preserver that handles all the log/error/process tracking from lpreserver. Not implemented in the tray yet, still needs a bit more work before it is ready to replace a lot of the code in LPTray.
Also add the beginnings of a couple new device management functions to the LPBackend.

  • Property mode set to 100644
File size: 12.0 KB
Line 
1#include "LPBackend.h"
2
3// ==============
4//     Informational
5// ==============
6QStringList LPBackend::listPossibleDatasets(){
7  QString cmd = "zpool list -H -o name";
8  QStringList out = LPBackend::getCmdOutput(cmd);
9  //Now process the output (one dataset per line - no headers)
10  QStringList list;
11  for(int i=0; i<out.length(); i++){
12    QString ds = out[i].section("/",0,0).simplified();
13    if(!ds.isEmpty()){ list << ds; }
14  }
15  list.removeDuplicates();
16   
17  return list; 
18}
19
20QStringList LPBackend::listDatasets(){
21  QString cmd = "lpreserver listcron";
22  QStringList out = LPBackend::getCmdOutput(cmd);
23  //Now process the output
24  QStringList list;
25  for(int i=2; i<out.length(); i++){ //skip the first two lines (headers)
26    QString ds = out[i].section(" - ",0,0).simplified();
27    if(!ds.isEmpty()){ list << ds; }
28  }
29   
30  return list;
31}
32
33QStringList LPBackend::listDatasetSubsets(QString dataset){
34  QString cmd = "zfs list -H -t filesystem -o name,mountpoint,mounted";
35  QStringList out = LPBackend::getCmdOutput(cmd);
36  //Now process the output (one dataset per line - no headers)
37  QStringList list;
38  for(int i=0; i<out.length(); i++){
39    if(out[i].startsWith(dataset+"/")){
40      if(out[i].section("\t",2,2,QString::SectionSkipEmpty).simplified() == "yes"){
41        QString ds = out[i].section("\t",1,1).simplified(); //save the mountpoint
42        if(!ds.isEmpty()){ list << ds; }
43      }
44    }
45  }
46  list.removeDuplicates();     
47   
48  return list;
49}
50
51QStringList LPBackend::listSnapshots(QString dsmountpoint){
52  //List all the snapshots available for the given dataset mountpoint
53  QDir dir(dsmountpoint+"/.zfs/snapshot");
54  QStringList list;
55  if(dir.exists()){
56    list = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed);
57  }
58  return list;
59}
60
61QStringList LPBackend::listLPSnapshots(QString dataset){
62  QString cmd = "lpreserver listsnap "+dataset;
63  QStringList out = LPBackend::getCmdOutput(cmd);
64  //Now process the output
65  QStringList list;
66  for(int i=out.length()-1; i>=0; i--){ //go in reverse order for proper time format (newest first)
67    if(out[i].startsWith(dataset+"@")){
68      QString snap = out[i].section("@",1,3).section(" ",0,0).simplified();;
69      if(!snap.isEmpty()){ list << snap; }
70    }
71  }
72   
73  return list; 
74}
75
76QStringList LPBackend::listReplicationTargets(){
77  QString cmd = "lpreserver replicate list";
78  QStringList out = LPBackend::getCmdOutput(cmd);
79  //Now process the output
80  QStringList list;
81  for(int i=0; i<out.length(); i++){
82    if(out[i].contains("->")){
83      QString ds = out[i].section("->",0,0).simplified();
84      if(!ds.isEmpty()){ list << ds; }
85    }
86  }
87   
88  return list;         
89}
90
91QStringList LPBackend::listCurrentStatus(){
92  QString cmd = "lpreserver status";
93  QStringList out = LPBackend::getCmdOutput(cmd);
94  //Now process the output     
95  QStringList list;
96  for(int i=2; i<out.length(); i++){ //first 2 lines are headers
97    //Format: <dataset>:::<lastsnapshot | NONE>:::<lastreplication | NONE>
98    if(out[i].isEmpty()){ continue; }
99    QString ds  = out[i].section(" - ",0,0).simplified();
100    QString snap = out[i].section(" - ",1,1).simplified();
101    QString rep = out[i].section(" - ",2,2).simplified();
102    if(snap == "NONE"){ snap = "-"; }
103    if(rep == "NONE"){ rep = "-"; }
104    list << ds +":::"+ snap+":::"+rep;
105  }
106   
107  return list;
108}
109
110// ==================
111//    Dataset Management
112// ==================
113bool LPBackend::setupDataset(QString dataset, int time, int numToKeep){
114  //Configure inputs
115  QString freq;
116  if(time == -30){ freq = "30min"; }
117  else if(time == -10){ freq = "10min"; }
118  else if(time == -5){ freq = "5min"; }
119  else if(time >= 0 && time < 24){ freq = "daily@"+QString::number(time); }
120  else{ freq = "hourly"; }
121 
122  //Create the command
123  QString cmd = "lpreserver cronsnap "+dataset+" start "+freq+" "+QString::number(numToKeep);
124  int ret = LPBackend::runCmd(cmd);
125   
126  return (ret == 0);
127}
128
129bool LPBackend::removeDataset(QString dataset){
130  QString cmd = "lpreserver cronsnap "+dataset+" stop";
131  int ret = LPBackend::runCmd(cmd);
132   
133  return (ret == 0);
134}
135
136bool LPBackend::datasetInfo(QString dataset, int& time, int& numToKeep){
137  QString cmd = "lpreserver listcron";
138  QStringList out = LPBackend::getCmdOutput(cmd);
139  //Now process the output
140  bool ok = false;
141  for(int i=0; i<out.length(); i++){
142    if(out[i].section(" - ",0,0).simplified() == dataset){
143      //Get time schedule (in integer format)
144      QString sch = out[i].section(" - ",1,1).simplified();
145      if(sch.startsWith("daily@")){ time = sch.section("@",1,1).simplified().toInt(); }
146      else if(sch=="5min"){time = -5;}
147      else if(sch=="10min"){time = -10;}
148      else if(sch=="30min"){time = -30;}
149      else{ time = -60; } //hourly
150      //Get total snapshots
151      numToKeep = out[i].section("- total:",1,1).simplified().toInt();
152      ok=true;
153      break;
154    }
155  }
156  //qDebug() << "lpreserver cronsnap:\n" << out << QString::number(time) << QString::number(numToKeep);
157   
158  return ok;
159}
160
161// ==================
162//    Snapshop Management
163// ==================
164void LPBackend::newSnapshot(QString dataset, QString snapshotname){
165  //This needs to run externally - since the snapshot is simply added to the queue, and the replication
166  //   afterwards may take a long time.
167  QString cmd = "lpreserver mksnap --replicate "+dataset+" "+snapshotname;
168  QProcess::startDetached(cmd);
169   
170  return;
171}
172
173bool LPBackend::removeSnapshot(QString dataset, QString snapshot){
174  QString cmd = "lpreserver rmsnap "+dataset +" "+snapshot;
175  int ret = LPBackend::runCmd(cmd);
176   
177  return (ret == 0);
178}
179
180bool LPBackend::revertSnapshot(QString dataset, QString snapshot){
181  QString cmd = "lpreserver revertsnap "+dataset +" "+snapshot;
182  int ret  = LPBackend::runCmd(cmd);
183   
184  return (ret == 0);
185}
186
187QString LPBackend::revertSnapshotFile(QString dsmountpoint, QString snapshot, QString filepath){
188  //Copy the given file from the snapshot back into the main dataset
189  // -- filepath: full path to the file in the snapshot directory
190 
191  //Check that the file path is complete and the file exists
192  if(!QFile::exists(filepath)){
193    //invalid file given
194    return "";
195  }
196  //Generate the new file path
197  QString newfilepath = filepath.replace(dsmountpoint+"/.zfs/snapshot/"+snapshot, dsmountpoint);       
198  if( QFile::exists(newfilepath) ){
199    //get the file extension
200    QString filename = newfilepath.section("/",-1);
201    QString ext = filename.section(".",-1);
202    if( !ext.isEmpty() && !filename.startsWith("."+ext) && ext!=filename){
203      newfilepath.chop(ext.length()+1);
204      newfilepath.append("-reversion."+ext);
205      int i=1;
206      //append a number to the end if a reversion file already exists
207      while(QFile::exists(newfilepath)){
208        newfilepath.chop(ext.length()+1);
209        newfilepath.append(QString::number(i)+"."+ext);
210      i  ++;
211      }
212    }else{
213      //File without an extension - just append a number
214      newfilepath.append("-reversion");
215      int i=1; 
216      QString npath = newfilepath;
217      while(QFile::exists(npath)){
218        npath = newfilepath.append(QString::number(i));
219        i++;
220      }
221      newfilepath = npath;
222    }
223  }
224  //perform the copy
225  bool ok = QFile::copy(filepath,newfilepath);
226  //return the path to the new file if the copy was successful
227  if(ok){ 
228    //reset the permissions on the reverted file to match the original
229    QFile::setPermissions(newfilepath, QFile::permissions(filepath));
230    return newfilepath;
231  }else{ return ""; }
232}
233
234// ==================
235//    Replication Management
236// ==================
237bool LPBackend::setupReplication(QString dataset, QString remotehost, QString user, int port, QString remotedataset, int time){
238  QString stime = "sync"; //synchronize on snapshot creation (default)
239  if(time >= 0 && time < 24){ stime = QString::number(time); } //daily at a particular hour (24 hour notation)
240 
241 
242  QString cmd = "lpreserver replicate add "+remotehost+" "+user+" "+ QString::number(port)+" "+dataset+" "+remotedataset+" "+stime;
243  int ret = LPBackend::runCmd(cmd);
244 
245  return (ret == 0);
246}
247
248bool LPBackend::removeReplication(QString dataset){
249  QString cmd = "lpreserver replicate remove "+dataset;
250  int ret = LPBackend::runCmd(cmd);     
251   
252  return (ret == 0);
253}
254
255bool LPBackend::replicationInfo(QString dataset, QString& remotehost, QString& user, int& port, QString& remotedataset, int& time){
256  QString cmd = "lpreserver replicate list";
257  QStringList out = LPBackend::getCmdOutput(cmd);
258  //Now process the output
259  bool ok = false;
260  for(int i=0; i<out.length(); i++){
261    if(out[i].contains("->") && out[i].startsWith(dataset)){
262      QString data = out[i].section("->",1,1);
263      user = data.section("@",0,0);
264      remotehost = data.section("@",1,1).section("[",0,0);
265      port = data.section("[",1,1).section("]",0,0).toInt();
266      remotedataset = data.section(":",1,1).section(" Time",0,0);
267      QString synchro = data.section("Time:",1,1).simplified();
268        if(synchro == "sync"){ time = -1; }
269        else{ time = synchro.toInt(); }
270      ok = true;
271      break;
272    }
273  }       
274   
275  return ok;
276}
277
278// ======================
279//          SSH Key Management
280// ======================
281bool LPBackend::setupSSHKey(QString remoteHost, QString remoteUser, int remotePort){
282  QString LPPATH = "/usr/local/share/lifePreserver";
283  QString cmd = "xterm -e \""+LPPATH+"/scripts/setup-ssh-keys.sh "+remoteUser+" "+remoteHost+" "+QString::number(remotePort)+"\"";
284  int ret = LPBackend::runCmd(cmd);
285  return (ret == 0);
286}
287
288QStringList LPBackend::findValidUSBDevices(){
289  //Return format: "<mountpoint> (<device node>")
290  QString cmd = "mount";
291  QStringList out = LPBackend::getCmdOutput(cmd);
292  //Now process the output
293  QStringList list;
294  for(int i=0; i<out.length(); i++){
295      if(out[i].startsWith("/dev/da") && out[i].contains("(msdosfs,")){
296      QString mountpoint = out[i].section(" on ",1,1).section("(",0,0).simplified();
297      QString devnode = out[i].section(" on ",0,0).section("/",-1).simplified();
298      list << mountpoint +" ("+devnode+")";
299    }
300  }
301  return list;
302}
303
304bool LPBackend::copySSHKey(QString mountPath, QString localHost){
305  QString publicKey = "/root/.ssh/id_rsa";
306  //copy the file onto the designated USB stick
307  if(!mountPath.endsWith("/")){ mountPath.append("/"); }
308  QDir lDir=mountPath + "lpreserver";
309  if ( ! lDir.exists() )
310     lDir.mkdir(lDir.path());
311
312  mountPath.append("lpreserver/"+localHost+"-id_rsa");
313
314  bool ok = QFile::copy(publicKey, mountPath);
315  return ok;
316}
317
318// ======================
319//          Device Management
320// ======================
321QStringList LPBackend::listDevices(){
322  //Scan the system for all valid da* and ada* devices (USB/SCSI, SATA)
323  //Return format: "<device node> (<device information>)"
324  QDir devDir("/dev");
325  QStringList devs = devDir.entryList(QStringList() << "da*"<<"ada*", QDir::System | QDir::NoSymLinks, QDir::Name);
326  QStringList camOut = LPBackend::getCmdOutput("camcontrol devlist");
327  QStringList output, flist;   
328  for(int i=0; i<devs.length(); i++){
329    flist = camOut.filter("("+devs[i]+",");
330    //still need to add an additional device filter to weed out devices currently in use.
331    if(!flist.isEmpty()){ output << devs[i] + " ("+flist[0].section(">",0,0).remove("<").simplified()+")"; }
332  }
333  return output;
334}
335
336bool LPBackend::isMounted(QString device){
337  qDebug() << "Device mount check not implemented yet";
338  return false;
339}
340
341bool LPBackend::unmountDevice(QString device){
342  qDebug() << "Device unmounting not implemented yet";
343  return false;
344}
345
346// =========================
347//             PRIVATE FUNCTIONS
348// =========================
349QStringList LPBackend::getCmdOutput(QString cmd){
350  QProcess *proc = new QProcess;
351  proc->setProcessChannelMode(QProcess::MergedChannels);
352  proc->start(cmd);
353  while(!proc->waitForFinished(300)){
354    QCoreApplication::processEvents();
355  }
356  QStringList out = QString(proc->readAllStandardOutput()).split("\n"); 
357  delete proc; 
358  return out;
359}
360
361int LPBackend::runCmd(QString cmd){
362  QProcess *proc = new QProcess;
363  proc->setProcessChannelMode(QProcess::MergedChannels);
364  proc->start(cmd);
365  while(!proc->waitForFinished(300)){
366    QCoreApplication::processEvents();
367  }
368  int ret = proc->exitCode();
369  delete proc; 
370  return ret;
371}
Note: See TracBrowser for help on using the repository browser.