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)
385 POOL_MEM tmp, command;
387 alist lst(10, not_owned_by_alist);
392 scan_for_commands(&lst);
394 foreach_alist(j, (&lst)) {
395 if (parser.parse_cmd(j->command) == bRC_OK) {
396 if ((i = parser.find_arg_with_value("job")) > 0) {
398 foreach_res(res, R_CLIENT) {
399 if (strcmp(res->hdr.name, j->component) == 0) {
404 foreach_res(res, R_DIRECTOR) {
405 if (strcmp(res->hdr.name, j->component) == 0) {
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);
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);
422 switch(msgbox.exec()) {
423 case QMessageBox::Ok:
424 Mmsg(command, "%s yes", j->command);
426 if (res->type == R_CLIENT) {
427 pm_strcat(command, " fdcalled=1");
430 // Build the command and run it!
432 connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
433 t->arg = command.c_str();
434 t->init(res, TASK_RUN);
438 case QMessageBox::Cancel:
439 case QMessageBox::Ignore:
448 void TSched::jobStarted(task *t)
450 Dmsg1(10, "-> jobid=%d\n", t->result.i);
455 bool TSched::scan_for_commands(alist *commands)
459 POOL_MEM fname(PM_FNAME), fname2(PM_FNAME);
460 bool ret=false, found=false;
461 struct dirent *entry = NULL, *result;
464 name_max = pathconf(".", _PC_NAME_MAX);
465 if (name_max < 1024) {
469 if (!(dp = opendir(command_dir))) {
471 Dmsg2(0, "Failed to open directory %s: ERR=%s\n",
472 command_dir, be.bstrerror());
476 entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
478 if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
484 if (strcmp(result->d_name, ".") == 0 ||
485 strcmp(result->d_name, "..") == 0) {
488 len = strlen(result->d_name);
492 if (strcmp(result->d_name + len - 5, ".bcmd") != 0) {
496 Mmsg(fname, "%s/%s", command_dir, result->d_name);
498 if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
499 continue; /* ignore directories & special files */
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