]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/tray-monitor/runjob.cpp
Preparation fixes: remove some warning
[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    Q_UNUSED(event)
385    POOL_MEM tmp, command;
386    TSchedJob *j;
387    alist lst(10, not_owned_by_alist);
388    arg_parser parser;
389    int i;
390    task *t;
391    RESMON *res;
392    scan_for_commands(&lst);
393
394    foreach_alist(j, (&lst)) {
395       if (parser.parse_cmd(j->command) == bRC_OK) {
396          if ((i = parser.find_arg_with_value("job")) > 0) {
397             QMessageBox msgbox;
398             foreach_res(res, R_CLIENT) {
399                if (strcmp(res->hdr.name, j->component) == 0) {
400                   break;
401                }
402             }
403             if (!res) {
404                foreach_res(res, R_DIRECTOR) {
405                   if (strcmp(res->hdr.name, j->component) == 0) {
406                      break;
407                   }
408                }
409             }
410             if (!res) {
411                msgbox.setIcon(QMessageBox::Information);
412                msgbox.setText(QString("Unable to find the component \"%1\" to run the job \"%2\".").arg(j->component, j->command));
413                msgbox.setStandardButtons(QMessageBox::Ignore);
414             } else {
415
416                msgbox.setIcon(QMessageBox::Information);
417                msgbox.setText(QString("The job \"%1\" will start automatically in few seconds...").arg(parser.argv[i]));
418                msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore);
419                msgbox.setDefaultButton(QMessageBox::Ok);
420                msgbox.button(QMessageBox::Ok)->animateClick(6000);
421             }
422             switch(msgbox.exec()) {
423                case QMessageBox::Ok:
424                   Mmsg(command, "%s yes", j->command);
425
426                   if (res->type == R_CLIENT) {
427                      pm_strcat(command, " fdcalled=1");
428                   }
429    
430                   // Build the command and run it!
431                   t = new task();
432                   connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
433                   t->arg = command.c_str();
434                   t->init(res, TASK_RUN);
435                   res->wrk->queue(t);
436
437                   break;
438                case QMessageBox::Cancel:
439                case QMessageBox::Ignore:
440                   break;
441             }
442          }
443       }
444       delete j;
445    }
446 }
447
448 void TSched::jobStarted(task *t)
449 {
450    Dmsg1(10, "-> jobid=%d\n", t->result.i);
451    t->deleteLater();
452 }
453
454
455 bool TSched::scan_for_commands(alist *commands)
456 {
457    int name_max, len;
458    DIR* dp = NULL;
459    POOL_MEM fname(PM_FNAME), fname2(PM_FNAME);
460    bool ret=false, found=false;
461    struct dirent *entry = NULL, *result;
462    struct stat statp;
463
464    name_max = pathconf(".", _PC_NAME_MAX);
465    if (name_max < 1024) {
466       name_max = 1024;
467    }
468
469    if (!(dp = opendir(command_dir))) {
470       berrno be;
471       Dmsg2(0, "Failed to open directory %s: ERR=%s\n",
472             command_dir, be.bstrerror());
473       goto bail_out;
474    }
475
476    entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
477    for ( ;; ) {
478       if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
479          if (!found) {
480             goto bail_out;
481          }
482          break;
483       }
484       if (strcmp(result->d_name, ".") == 0 ||
485           strcmp(result->d_name, "..") == 0) {
486          continue;
487       }
488       len = strlen(result->d_name);
489       if (len <= 5) {
490          continue;
491       }
492       if (strcmp(result->d_name + len - 5, ".bcmd") != 0) {
493          continue;
494       }
495
496       Mmsg(fname, "%s/%s", command_dir, result->d_name);
497
498       if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
499          continue;                 /* ignore directories & special files */
500       }
501
502       if (read_command_file(fname.c_str(), commands, statp.st_mtime)) {
503          Mmsg(fname2, "%s.ok", fname.c_str());
504          unlink(fname2.c_str());
505          rename(fname.c_str(), fname2.c_str()); // TODO: We should probably unlink the file
506       }
507    }
508 bail_out:
509    if (entry) {
510       free(entry);
511    }
512    if (dp) {
513       closedir(dp);
514    }
515    return ret;
516 }