]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/tray-monitor/runjob.cpp
e6204ee45bb610250bdabb1b101721f6401d5198
[bacula/bacula] / bacula / src / qt-console / tray-monitor / runjob.cpp
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19
20 #include "runjob.h"
21 #include <QMessageBox>
22
23 static void fillcombo(QComboBox *cb, alist *lst, bool addempty=true)
24 {
25    if (lst && lst->size() > 0) {
26       QStringList list;
27       char *str;
28       if (addempty) {
29          list << QString("");
30       }
31       foreach_alist(str, lst) {
32          list << QString(str);
33       }
34       cb->addItems(list);
35    } else {
36       cb->setEnabled(false);
37    }
38 }
39
40 RunJob::RunJob(RESMON *r): QDialog(), res(r), tabAdvanced(NULL)
41 {
42    int nbjob;
43    if (res->jobs->size() == 0) {
44       QMessageBox msgBox;
45       msgBox.setText(_("This restricted console does not have access to Backup jobs"));
46       msgBox.setIcon(QMessageBox::Warning);
47       msgBox.exec();
48       deleteLater();
49       return;
50
51    }
52
53    ui.setupUi(this);
54    setModal(true);
55    connect(ui.cancelButton, SIGNAL(clicked()), this, SLOT(close_cb()));
56    connect(ui.okButton, SIGNAL(clicked()), this, SLOT(runjob()));
57    connect(ui.jobCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(jobChanged(int)));
58    connect(ui.levelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(levelChanged(int)));
59    ui.dateTimeEdit->setMinimumDate(QDate::currentDate());
60    ui.dateTimeEdit->setMaximumDate(QDate::currentDate().addDays(7));
61    ui.dateTimeEdit->setDate(QDate::currentDate());
62    ui.dateTimeEdit->setTime(QTime::currentTime());
63    ui.boxEstimate->setVisible(false);
64
65    res->mutex->lock();
66    nbjob = res->jobs->size();
67    fillcombo(ui.jobCombo,    res->jobs, (nbjob > 1));
68    fillcombo(ui.clientCombo, res->clients);
69    fillcombo(ui.filesetCombo,res->filesets);
70    fillcombo(ui.poolCombo,   res->pools);
71    fillcombo(ui.storageCombo,res->storages);
72    fillcombo(ui.catalogCombo,res->catalogs);
73    res->mutex->unlock();
74    connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChange(int)));
75    QStringList levels;
76    levels << "" << "Incremental" << "Differential"  << "Full";
77    ui.levelCombo->addItems(levels);
78
79    MONITOR *m = (MONITOR*) GetNextRes(R_MONITOR, NULL);
80    if (!m->display_advanced_options) {
81       tabAdvanced = ui.tabWidget->widget(1);
82       ui.tabWidget->removeTab(1);
83    }
84
85    show();
86 };
87
88 void RunJob::tabChange(int idx)
89 {
90    QString q = ui.tabWidget->tabText(idx);
91    if (q.contains("Advanced")) {
92       if (ui.jobCombo->currentText().compare("") == 0) {
93          pm_strcpy(curjob, "");
94          ui.tab2->setEnabled(false);
95
96       } else if (ui.jobCombo->currentText().compare(curjob.c_str()) != 0) {
97          task *t = new task();
98          char *job = bstrdup(ui.jobCombo->currentText().toUtf8().data());
99          pm_strcpy(curjob, job); // Keep the job name to not refresh the Advanced tab the next time
100
101          Dmsg1(10, "get defaults for %s\n", job);
102          res->mutex->lock();
103          bfree_and_null(res->defaults.job);
104          res->defaults.job = job;
105          res->mutex->unlock();
106
107          ui.tab2->setEnabled(false);
108          connect(t, SIGNAL(done(task *)), this, SLOT(fill_defaults(task *)), Qt::QueuedConnection);
109          t->init(res, TASK_DEFAULTS);
110          res->wrk->queue(t);
111       }
112    }
113 }
114
115 void RunJob::runjob()
116 {
117    POOL_MEM tmp;
118    char *p;
119
120    p = ui.jobCombo->currentText().toUtf8().data();
121    if (!p || !*p) {
122       QMessageBox msgBox;
123       msgBox.setText(_("Nothing selected"));
124       msgBox.setIcon(QMessageBox::Warning);
125       msgBox.exec();
126       return;
127    }
128
129    Mmsg(command, "run job=\"%s\" yes", p);
130
131    if (strcmp(p, NPRTB(res->defaults.job)) == 0 || strcmp("", NPRTB(res->defaults.job)) == 0) {
132       p = ui.storageCombo->currentText().toUtf8().data();
133       if (p && *p && strcmp(p, NPRTB(res->defaults.storage)) != 0) {
134          Mmsg(tmp, " storage=\"%s\"", p);
135          pm_strcat(command, tmp.c_str());
136       }
137
138       p = ui.clientCombo->currentText().toUtf8().data();
139       if (p && *p && strcmp(p, NPRTB(res->defaults.client)) != 0) {
140          Mmsg(tmp, " client=\"%s\"", p);
141          pm_strcat(command, tmp.c_str());
142       }
143
144       p = ui.levelCombo->currentText().toUtf8().data();
145       if (p && *p && strcmp(p, NPRTB(res->defaults.level)) != 0) {
146          Mmsg(tmp, " level=\"%s\"", p);
147          pm_strcat(command, tmp.c_str());
148       }
149
150       p = ui.poolCombo->currentText().toUtf8().data();
151       if (p && *p && strcmp(p, NPRTB(res->defaults.pool)) != 0) {
152          Mmsg(tmp, " pool=\"%s\"", p);
153          pm_strcat(command, tmp.c_str());
154       }
155
156       p = ui.filesetCombo->currentText().toUtf8().data();
157       if (p && *p && strcmp(p, NPRTB(res->defaults.fileset)) != 0) {
158          Mmsg(tmp, " fileset=\"%s\"", p);
159          pm_strcat(command, tmp.c_str());
160       }
161
162       if (res->defaults.priority && res->defaults.priority != ui.prioritySpin->value()) {
163          Mmsg(tmp, " priority=\"%d\"", res->defaults.priority);
164          pm_strcat(command, tmp.c_str());
165       }
166    }
167
168    QDate dnow = QDate::currentDate();
169    QTime tnow = QTime::currentTime();
170    QDate dval = ui.dateTimeEdit->date();
171    QTime tval = ui.dateTimeEdit->time();
172
173    if (dval > dnow || (dval == dnow && tval > tnow)) {
174       Mmsg(tmp, " when=\"%s %s\"", dval.toString("yyyy-MM-dd").toUtf8().data(), tval.toString("hh:mm:00").toUtf8().data());
175       pm_strcat(command, tmp.c_str());
176    }
177
178    if (res->type == R_CLIENT) {
179       pm_strcat(command, " fdcalled=1");
180    }
181    
182    // Build the command and run it!
183    task *t = new task();
184    connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
185    t->arg = command.c_str();
186    t->init(res, TASK_RUN);
187    res->wrk->queue(t);
188 }
189
190 void RunJob::jobStarted(task *t)
191 {
192    Dmsg1(10, "%s\n", command.c_str());
193    Dmsg1(10, "-> jobid=%d\n", t->result.i);
194    deleteLater();
195    delete t;
196 }
197
198 void RunJob::close_cb(task *t)
199 {
200    deleteLater();
201    delete t;
202 }
203
204 void RunJob::close_cb()
205 {
206    task *t = new task();
207    connect(t, SIGNAL(done(task *)), this, SLOT(close_cb(task *)), Qt::QueuedConnection);
208    t->init(res, TASK_DISCONNECT);
209    res->wrk->queue(t);
210 }
211
212 void RunJob::jobChanged(int)
213 {
214    char *p;
215    ui.levelCombo->setCurrentIndex(0);
216    ui.storageCombo->setCurrentIndex(0);
217    ui.filesetCombo->setCurrentIndex(0);
218    ui.clientCombo->setCurrentIndex(0);
219    ui.storageCombo->setCurrentIndex(0);
220    ui.poolCombo->setCurrentIndex(0);
221    ui.catalogCombo->setCurrentIndex(0);
222
223    p = ui.jobCombo->currentText().toUtf8().data();
224    if (p && *p) {
225       task *t = new task();
226       pm_strcpy(info, p);
227       connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection);
228       t->arg = info.c_str();    // Jobname
229       t->arg2 = NULL;           // Level
230       t->init(res, TASK_INFO);
231       res->wrk->queue(t);
232    }
233 }
234
235 void RunJob::levelChanged(int)
236 {
237    char *p;
238    p = ui.jobCombo->currentText().toUtf8().data();
239    if (p && *p) {
240       pm_strcpy(info, p);      
241       p = ui.levelCombo->currentText().toUtf8().data();
242       if (p && *p) {
243          task *t = new task();
244          pm_strcpy(level, p);
245          connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection);
246          t->arg = info.c_str();    // Jobname
247          t->arg2 = level.c_str();  // Level
248          t->init(res, TASK_INFO);
249          res->wrk->queue(t);
250       }
251    }
252 }
253
254 void RunJob::jobInfo(task *t)
255 {
256    char ed1[50];
257    res->mutex->lock();
258    if (res->infos.CorrNbJob == 0) {
259       ui.boxEstimate->setVisible(false);
260    } else {
261       QString t;
262       edit_uint64_with_suffix(res->infos.JobBytes, ed1);
263       strncat(ed1, "B", sizeof(ed1));
264       ui.labelJobBytes->setText(QString(ed1));
265       ui.labelJobFiles->setText(QString(edit_uint64_with_commas(res->infos.JobFiles, ed1)));
266       ui.labelJobLevel->setText(QString(job_level_to_str(res->infos.JobLevel)));
267       t = tr("Computed over %1 job%2, the correlation is %3/100.").arg(res->infos.CorrNbJob).arg(res->infos.CorrNbJob>1?"s":"").arg(res->infos.CorrJobBytes);
268       ui.labelJobBytes_2->setToolTip(t);
269       t = tr("Computed over %1 job%2, The correlation is %3/100.").arg(res->infos.CorrNbJob).arg(res->infos.CorrNbJob>1?"s":"").arg(res->infos.CorrJobFiles);
270       ui.labelJobFiles_2->setToolTip(t);
271       ui.boxEstimate->setVisible(true);
272    }
273    res->mutex->unlock();
274    t->deleteLater();
275 }
276
277 static void set_combo(QComboBox *dest, char *str)
278 {
279    if (str) {
280       int idx = dest->findText(QString(str), Qt::MatchExactly);
281       if (idx >= 0) {
282          dest->setCurrentIndex(idx);
283       }
284    }
285 }
286
287 void RunJob::fill_defaults(task *t)
288 {
289    if (t->status == true) {
290       res->mutex->lock();
291       set_combo(ui.levelCombo, res->defaults.level);
292       set_combo(ui.filesetCombo, res->defaults.fileset);
293       set_combo(ui.clientCombo, res->defaults.client);
294       set_combo(ui.storageCombo, res->defaults.storage);
295       set_combo(ui.poolCombo, res->defaults.pool);
296       set_combo(ui.catalogCombo, res->defaults.catalog);
297       res->mutex->unlock();
298    }
299
300    ui.tab2->setEnabled(true);
301    t->deleteLater();
302 }
303
304 RunJob::~RunJob()
305 {
306    Dmsg0(10, "~RunJob()\n");
307    if (tabAdvanced) {
308       delete tabAdvanced;
309    }
310 }
311
312 void TSched::init(const char *cmd_dir)
313 {
314    bool started = (timer >= 0);
315    if (started) {
316       stop();
317    }
318
319    bfree_and_null(command_dir);
320    command_dir = bstrdup(cmd_dir);
321
322    if (started) {
323       start();
324    }
325 }
326
327 TSched::TSched() {
328    timer = -1;
329    command_dir = NULL;
330 }
331
332 TSched::~TSched() {
333    if (timer >= 0) {
334       stop();
335    }
336    bfree_and_null(command_dir);
337 }
338
339 #ifndef HAVE_READDIR_R
340 int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
341 #else
342 #include <dirent.h>
343 #endif
344
345 bool TSched::read_command_file(const char *file, alist *lst, btime_t mtime)
346 {
347    POOLMEM *line;
348    bool ret=false;
349    char *p;
350    TSchedJob *s;
351    Dmsg1(50, "open command file %s\n", file);
352    FILE *fp = fopen(file, "r");
353    if (!fp) {
354       return false;
355    }
356    line = get_pool_memory(PM_FNAME);
357
358    /* Get the first line, client/component:command */
359    while (bfgets(line, fp) != NULL) {
360       strip_trailing_junk(line);
361       Dmsg1(50, "%s\n", line);
362       if (line[0] == '#') {
363          continue;
364       }
365
366       if ((p = strchr(line, ':')) != NULL) {
367          *p=0;
368          s = new TSchedJob(line, p+1, mtime);
369          lst->append(s);
370          ret = true;
371       }
372    }
373
374    free_pool_memory(line);
375    fclose(fp);
376    return ret;
377 }
378
379 #include "lib/plugins.h"
380 #include "lib/cmd_parser.h"
381
382 void TSched::timerEvent(QTimerEvent *event)
383 {
384    POOL_MEM tmp, command;
385    TSchedJob *j;
386    alist lst(10, not_owned_by_alist);
387    arg_parser parser;
388    int i;
389    task *t;
390    RESMON *res;
391    scan_for_commands(&lst);
392
393    foreach_alist(j, (&lst)) {
394       if (parser.parse_cmd(j->command) == bRC_OK) {
395          if ((i = parser.find_arg_with_value("job")) > 0) {
396             QMessageBox msgbox;
397             foreach_res(res, R_CLIENT) {
398                if (strcmp(res->hdr.name, j->component) == 0) {
399                   break;
400                }
401             }
402             if (!res) {
403                foreach_res(res, R_DIRECTOR) {
404                   if (strcmp(res->hdr.name, j->component) == 0) {
405                      break;
406                   }
407                }
408             }
409             if (!res) {
410                msgbox.setIcon(QMessageBox::Information);
411                msgbox.setText(QString("Unable to find the component \"%1\" to run the job \"%2\".").arg(j->component, j->command));
412                msgbox.setStandardButtons(QMessageBox::Ignore);
413             } else {
414
415                msgbox.setIcon(QMessageBox::Information);
416                msgbox.setText(QString("The job \"%1\" will start automatically in few seconds...").arg(parser.argv[i]));
417                msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore);
418                msgbox.setDefaultButton(QMessageBox::Ok);
419                msgbox.button(QMessageBox::Ok)->animateClick(6000);
420             }
421             switch(msgbox.exec()) {
422                case QMessageBox::Ok:
423                   Mmsg(command, "%s yes", j->command);
424
425                   if (res->type == R_CLIENT) {
426                      pm_strcat(command, " fdcalled=1");
427                   }
428    
429                   // Build the command and run it!
430                   t = new task();
431                   connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
432                   t->arg = command.c_str();
433                   t->init(res, TASK_RUN);
434                   res->wrk->queue(t);
435
436                   break;
437                case QMessageBox::Cancel:
438                case QMessageBox::Ignore:
439                   break;
440             }
441          }
442       }
443       delete j;
444    }
445 }
446
447 void TSched::jobStarted(task *t)
448 {
449    Dmsg1(10, "-> jobid=%d\n", t->result.i);
450    t->deleteLater();
451 }
452
453
454 bool TSched::scan_for_commands(alist *commands)
455 {
456    int name_max, len;
457    DIR* dp = NULL;
458    POOL_MEM fname(PM_FNAME), fname2(PM_FNAME);
459    bool ret=false, found=false;
460    struct dirent *entry = NULL, *result;
461    struct stat statp;
462
463    name_max = pathconf(".", _PC_NAME_MAX);
464    if (name_max < 1024) {
465       name_max = 1024;
466    }
467
468    if (!(dp = opendir(command_dir))) {
469       berrno be;
470       Dmsg2(0, "Failed to open directory %s: ERR=%s\n",
471             command_dir, be.bstrerror());
472       goto bail_out;
473    }
474
475    entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
476    for ( ;; ) {
477       if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
478          if (!found) {
479             goto bail_out;
480          }
481          break;
482       }
483       if (strcmp(result->d_name, ".") == 0 ||
484           strcmp(result->d_name, "..") == 0) {
485          continue;
486       }
487       len = strlen(result->d_name);
488       if (len <= 5) {
489          continue;
490       }
491       if (strcmp(result->d_name + len - 5, ".bcmd") != 0) {
492          continue;
493       }
494
495       Mmsg(fname, "%s/%s", command_dir, result->d_name);
496
497       if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
498          continue;                 /* ignore directories & special files */
499       }
500
501       if (read_command_file(fname.c_str(), commands, statp.st_mtime)) {
502          Mmsg(fname2, "%s.ok", fname.c_str());
503          unlink(fname2.c_str());
504          rename(fname.c_str(), fname2.c_str()); // TODO: We should probably unlink the file
505       }
506    }
507 bail_out:
508    if (entry) {
509       free(entry);
510    }
511    if (dp) {
512       closedir(dp);
513    }
514    return ret;
515 }