2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
21 #include <QMessageBox>
23 static void fillcombo(QComboBox *cb, alist *lst, bool addempty=true)
25 if (lst && lst->size() > 0) {
31 foreach_alist(str, lst) {
36 cb->setEnabled(false);
40 RunJob::RunJob(RESMON *r): QDialog(), res(r), tabAdvanced(NULL)
43 if (res->jobs->size() == 0) {
45 msgBox.setText(_("This restricted console does not have access to Backup jobs"));
46 msgBox.setIcon(QMessageBox::Warning);
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);
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);
74 connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChange(int)));
76 levels << "" << "Incremental" << "Differential" << "Full";
77 ui.levelCombo->addItems(levels);
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);
88 void RunJob::tabChange(int idx)
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);
96 } else if (ui.jobCombo->currentText().compare(curjob.c_str()) != 0) {
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
101 Dmsg1(10, "get defaults for %s\n", job);
103 bfree_and_null(res->defaults.job);
104 res->defaults.job = job;
105 res->mutex->unlock();
107 ui.tab2->setEnabled(false);
108 connect(t, SIGNAL(done(task *)), this, SLOT(fill_defaults(task *)), Qt::QueuedConnection);
109 t->init(res, TASK_DEFAULTS);
115 void RunJob::runjob()
120 p = ui.jobCombo->currentText().toUtf8().data();
123 msgBox.setText(_("Nothing selected"));
124 msgBox.setIcon(QMessageBox::Warning);
129 Mmsg(command, "run job=\"%s\" yes", p);
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());
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());
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());
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());
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());
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());
168 QDate dnow = QDate::currentDate();
169 QTime tnow = QTime::currentTime();
170 QDate dval = ui.dateTimeEdit->date();
171 QTime tval = ui.dateTimeEdit->time();
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());
178 if (res->type == R_CLIENT) {
179 pm_strcat(command, " fdcalled=1");
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);
190 void RunJob::jobStarted(task *t)
192 Dmsg1(10, "%s\n", command.c_str());
193 Dmsg1(10, "-> jobid=%d\n", t->result.i);
198 void RunJob::close_cb(task *t)
204 void RunJob::close_cb()
206 task *t = new task();
207 connect(t, SIGNAL(done(task *)), this, SLOT(close_cb(task *)), Qt::QueuedConnection);
208 t->init(res, TASK_DISCONNECT);
212 void RunJob::jobChanged(int)
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);
223 p = ui.jobCombo->currentText().toUtf8().data();
225 task *t = new task();
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);
235 void RunJob::levelChanged(int)
238 p = ui.jobCombo->currentText().toUtf8().data();
241 p = ui.levelCombo->currentText().toUtf8().data();
243 task *t = new task();
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);
254 void RunJob::jobInfo(task *t)
258 if (res->infos.CorrNbJob == 0) {
259 ui.boxEstimate->setVisible(false);
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);
273 res->mutex->unlock();
277 static void set_combo(QComboBox *dest, char *str)
280 int idx = dest->findText(QString(str), Qt::MatchExactly);
282 dest->setCurrentIndex(idx);
287 void RunJob::fill_defaults(task *t)
289 if (t->status == true) {
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();
300 ui.tab2->setEnabled(true);
306 Dmsg0(10, "~RunJob()\n");
312 void TSched::init(const char *cmd_dir)
314 bool started = (timer >= 0);
319 bfree_and_null(command_dir);
320 command_dir = bstrdup(cmd_dir);
336 bfree_and_null(command_dir);
339 #ifndef HAVE_READDIR_R
340 int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
345 bool TSched::read_command_file(const char *file, alist *lst, btime_t mtime)
351 Dmsg1(50, "open command file %s\n", file);
352 FILE *fp = fopen(file, "r");
356 line = get_pool_memory(PM_FNAME);
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] == '#') {
366 if ((p = strchr(line, ':')) != NULL) {
368 s = new TSchedJob(line, p+1, mtime);
374 free_pool_memory(line);
379 #include "lib/plugins.h"
380 #include "lib/cmd_parser.h"
382 void TSched::timerEvent(QTimerEvent *event)
384 POOL_MEM tmp, command;
386 alist lst(10, not_owned_by_alist);
391 scan_for_commands(&lst);
393 foreach_alist(j, (&lst)) {
394 if (parser.parse_cmd(j->command) == bRC_OK) {
395 if ((i = parser.find_arg_with_value("job")) > 0) {
397 foreach_res(res, R_CLIENT) {
398 if (strcmp(res->hdr.name, j->component) == 0) {
403 foreach_res(res, R_DIRECTOR) {
404 if (strcmp(res->hdr.name, j->component) == 0) {
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);
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);
421 switch(msgbox.exec()) {
422 case QMessageBox::Ok:
423 Mmsg(command, "%s yes", j->command);
425 if (res->type == R_CLIENT) {
426 pm_strcat(command, " fdcalled=1");
429 // Build the command and run it!
431 connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
432 t->arg = command.c_str();
433 t->init(res, TASK_RUN);
437 case QMessageBox::Cancel:
438 case QMessageBox::Ignore:
447 void TSched::jobStarted(task *t)
449 Dmsg1(10, "-> jobid=%d\n", t->result.i);
454 bool TSched::scan_for_commands(alist *commands)
458 POOL_MEM fname(PM_FNAME), fname2(PM_FNAME);
459 bool ret=false, found=false;
460 struct dirent *entry = NULL, *result;
463 name_max = pathconf(".", _PC_NAME_MAX);
464 if (name_max < 1024) {
468 if (!(dp = opendir(command_dir))) {
470 Dmsg2(0, "Failed to open directory %s: ERR=%s\n",
471 command_dir, be.bstrerror());
475 entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
477 if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
483 if (strcmp(result->d_name, ".") == 0 ||
484 strcmp(result->d_name, "..") == 0) {
487 len = strlen(result->d_name);
491 if (strcmp(result->d_name + len - 5, ".bcmd") != 0) {
495 Mmsg(fname, "%s/%s", command_dir, result->d_name);
497 if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
498 continue; /* ignore directories & special files */
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