From: Kern Sibbald Date: Mon, 10 Jul 2017 15:50:29 +0000 (+0200) Subject: Add new tray-monitor files that were omitted in the backport from Enterprise X-Git-Tag: Release-9.0.1~7 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=a34f61be3e65120b8b22d306c0718d7a5aec7bc1;p=bacula%2Fbacula Add new tray-monitor files that were omitted in the backport from Enterprise --- diff --git a/bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in b/bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in new file mode 100644 index 0000000000..4a9eff7b17 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in @@ -0,0 +1,32 @@ +# +# Bacula Tray Monitor Configuration File +# +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# + +Monitor { + Name = @basename@-mon + RefreshInterval = 120 seconds +} + +Client { + Name = @basename@-fd + Password = "@mon_fd_password@" # password for FileDaemon + Address = @hostname@ + Port = @fd_port@ +} + +#Storage { +# Name = @basename@-sd +# Address = @hostname@ +# Port = @sd_port@ +# Password = "@mon_sd_password@" # password for StorageDaemon +#} +# +#Director { +# Name = @basename@-dir +# Address = @hostname@ +# Port = @dir_port@ +# Password = "@mon_dir_password@" # password for the Directors +#} diff --git a/bacula/src/qt-console/tray-monitor/conf.h b/bacula/src/qt-console/tray-monitor/conf.h new file mode 100644 index 0000000000..e2cb528026 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/conf.h @@ -0,0 +1,85 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef CONF_H +#define CONF_H + +#include "common.h" +#include "ui_main-conf.h" +#include "ui_res-conf.h" +#include "tray_conf.h" + +class Conf: public QDialog +{ + Q_OBJECT + +private: + CONFIG *config; + RES_HEAD **rhead; +public: + int items; + QLineEdit::EchoMode passtype; + Ui::Conf UIConf; + Conf(); + ~Conf(); + bool parse_config(); + void addResource(RESMON *res, const char *title); + void addRes(int type, const char *title); /* create the resource */ +public slots: + void accept(); + void selectCommandDir(); + void addDir(); + void addStore(); + void addClient(); + void togglePassword(); +}; + +class ConfTab: public QWidget +{ + Q_OBJECT + +public: + Ui::ResConf ui; + RESMON *res; + int type; + bool new_resource; + ConfTab(RESMON *r): QWidget() { + res = r; + type = r->type; + new_resource = r->new_resource; + ui.setupUi(this); + connect(ui.bpDelete, SIGNAL(clicked()), this, SLOT(disable())); + }; + ~ConfTab() { + if (new_resource && res) { + free_resource((RES*) res, res->type); + res = NULL; + } + }; +public slots: + void disable() { + setEnabled(false); + }; + void selectCaCertificateFile(); + void selectCaCertificateDir(); + void selectCertificate(); + void selectKey(); +}; + +#endif diff --git a/bacula/src/qt-console/tray-monitor/install_conf_file.in b/bacula/src/qt-console/tray-monitor/install_conf_file.in index 6b9d27823b..3698afb442 100755 --- a/bacula/src/qt-console/tray-monitor/install_conf_file.in +++ b/bacula/src/qt-console/tray-monitor/install_conf_file.in @@ -1,4 +1,7 @@ #!/bin/sh +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# sbindir=@sbindir@ sysconfdir=@sysconfdir@ diff --git a/bacula/src/qt-console/tray-monitor/main-conf.ui b/bacula/src/qt-console/tray-monitor/main-conf.ui new file mode 100644 index 0000000000..4e023ab6bd --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/main-conf.ui @@ -0,0 +1,350 @@ + + + Conf + + + + 0 + 0 + 556 + 337 + + + + Configuration + + + + + + 0 + + + + Monitor Configuration + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + The Monitor name will be used during the authentication phase. + + + Name: + + + + + + + The Monitor name will be used during the authentication phase. + + + 127 + + + + + + + Refresh Interval: + + + + + + + 5 + + + 9999 + + + 120 + + + + + + + Specify the "Command Directory" where the tray-monitor program will check regularly for jobs to run + + + Command Directory: + + + + + + + + + Specify the "Command Directory" where the tray-monitor program will check regularly for jobs to run + + + + + + + ... + + + + + + + + + Display or Hide advanced options in the "Run Job" window + + + Display Advanced Options: + + + + + + + Display or Hide advanced options in the "Run Job" window + + + + + + + + + + + + + + + + + + Save and Apply the changes + + + Save + + + + :/images/label.png:/images/label.png + + + + + + + Cancel + + + + :/images/A.png:/images/A.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Show/Hide Passwords + + + Password + + + + :/images/zoom.png:/images/zoom.png + + + + + + + Add Client resource to monitor + + + Client + + + + :/images/mark.png:/images/mark.png + + + + + + + Add Storage resource to monitor + + + Storage + + + + :/images/mark.png:/images/mark.png + + + + + + + Add Director resource to monitor + + + Director + + + + :/images/mark.png:/images/mark.png + + + + + + + + + + + + + bpSave + clicked() + Conf + accept() + + + 511 + 30 + + + 521 + 46 + + + + + bpCancel + clicked() + Conf + close() + + + 511 + 76 + + + 521 + 159 + + + + + bpStrip + clicked() + Conf + togglePassword() + + + 511 + 178 + + + 496 + 142 + + + + + bpAddClient + clicked() + Conf + addClient() + + + 511 + 239 + + + 521 + 245 + + + + + bpAddStorage + clicked() + Conf + addStore() + + + 511 + 272 + + + 521 + 289 + + + + + bpAddDir + clicked() + Conf + addDir() + + + 511 + 313 + + + 521 + 331 + + + + + bpCommandDir + clicked() + Conf + selectCommandDir() + + + 405 + 135 + + + 466 + 112 + + + + + + togglePassword() + addClient() + addStore() + addDir() + selectCommandDir() + + diff --git a/bacula/src/qt-console/tray-monitor/res-conf.ui b/bacula/src/qt-console/tray-monitor/res-conf.ui new file mode 100644 index 0000000000..7cf21a7837 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/res-conf.ui @@ -0,0 +1,565 @@ + + + ResConf + + + + 0 + 0 + 417 + 541 + + + + Form + + + + + + General + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + The Name will be used only in the Tray Monitor interface + + + Name: + + + + + + + The Name will be used only in the Tray Monitor interface + + + 127 + + + + + + + Description: + + + + + + + 512 + + + + + + + Password: + + + + + + + + + + 127 + + + QLineEdit::PasswordEchoOnEdit + + + + + + + Address: + + + + + + + 1024 + + + + + + + Port: + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + 5 + + + + + + + Timeout: + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + 5 + + + + + + + Use Client Initiated backup/restore feature + + + Remote + + + + + + + Use Client Initiated backup/restore feature + + + + + + + + + + Update the tray monitor icon with the status of this component + + + Monitor: + + + + + + + Update the tray monitor icon with the status of this component + + + + + + + + + + + + + + + + + Use SetIp: + + + + + + + + + + TLS + + + + + + ... + + + + + + + CA Certificate File: + + + + + + + Enabled + + + true + + + + + + + ... + + + + + + + Key File: + + + + + + + + + + ... + + + + + + + Certificate File: + + + + + + + + + + + + + + + + CA Certificate Directory: + + + + + + + ... + + + + + cbTLSEnabled + editCaCertificateFile + label_5 + label_6 + editCaCertificateDir + label_7 + editCertificate + label_8 + editKey + bpCaCertificateFile + bpCaCertificateDir + bpCertificate + bpKey + + + + + + + 64 + 16777215 + + + + + 64 + 0 + + + + + + + + :/images/purge.png:/images/purge.png + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + editName + editDescription + editPassword + editAddress + editPort + editTimeout + cbRemote + cbTLSEnabled + editCaCertificateFile + bpCaCertificateFile + editCaCertificateDir + bpCaCertificateDir + editCertificate + bpCertificate + editKey + bpKey + + + + + + + bpCaCertificateFile + clicked() + ResConf + selectCaCertificateFile() + + + 461 + 294 + + + 521 + 247 + + + + + bpCaCertificateDir + clicked() + ResConf + selectCaCertificateDir() + + + 452 + 334 + + + 501 + 355 + + + + + bpCertificate + clicked() + ResConf + selectCertificate() + + + 459 + 364 + + + 495 + 384 + + + + + bpKey + clicked() + ResConf + selectKey() + + + 461 + 395 + + + 481 + 410 + + + + + cbTLSEnabled + toggled(bool) + editCaCertificateFile + setEnabled(bool) + + + 132 + 271 + + + 249 + 291 + + + + + cbTLSEnabled + toggled(bool) + editCaCertificateDir + setEnabled(bool) + + + 120 + 274 + + + 203 + 325 + + + + + cbTLSEnabled + toggled(bool) + editCertificate + setEnabled(bool) + + + 68 + 271 + + + 220 + 360 + + + + + cbTLSEnabled + toggled(bool) + editKey + setEnabled(bool) + + + 51 + 275 + + + 288 + 392 + + + + + cbTLSEnabled + toggled(bool) + bpCaCertificateFile + setEnabled(bool) + + + 161 + 267 + + + 449 + 291 + + + + + cbTLSEnabled + toggled(bool) + bpCaCertificateDir + setEnabled(bool) + + + 145 + 271 + + + 455 + 329 + + + + + cbTLSEnabled + toggled(bool) + bpCertificate + setEnabled(bool) + + + 140 + 266 + + + 459 + 358 + + + + + cbTLSEnabled + toggled(bool) + bpKey + setEnabled(bool) + + + 118 + 272 + + + 458 + 389 + + + + + + selectCaCertificateFile() + selectCaCertificateDir() + selectCertificate() + selectKey() + + diff --git a/bacula/src/qt-console/tray-monitor/run.ui b/bacula/src/qt-console/tray-monitor/run.ui new file mode 100644 index 0000000000..4ed29af131 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/run.ui @@ -0,0 +1,379 @@ + + + runForm + + + + 0 + 0 + 568 + 407 + + + + + 0 + 0 + + + + Run job + + + + + + + 16777215 + 30 + + + + + 11 + + + + <h3>Run a Job</h3> + + + + + + + + 0 + 0 + + + + + 0 + 5 + + + + Qt::Horizontal + + + + + + + + + + + + :/images/runit.png + + + + + + + 0 + + + + Properties + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Job: + + + jobCombo + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + + + + When: + + + dateTimeEdit + + + + + + + QDateTimeEdit::YearSection + + + yyyy-MM-dd hh:mm:ss + + + true + + + + + + + + + + <html><head/><body><p>Job statistics computed from the Catalog with previous jobs.</p><p>For accurate information, it is possible to use the bconsole &quot;estimate&quot; command.</p></body></html> + + + Estimate: + + + + + + Job Bytes: + + + + + + + + + + + + + + Job Files: + + + + + + + + + + + + + + Level: + + + + + + + + + + + + + + + + + + Advanced + + + + + + Level: + + + levelCombo + + + + + + + + + + Client: + + + clientCombo + + + + + + + + + + FileSet: + + + filesetCombo + + + + + + + + + + Pool: + + + poolCombo + + + + + + + + + + Storage: + + + storageCombo + + + + + + + + + + Catalog: + + + + + + + + + + Priority: + + + prioritySpin + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + 60 + 0 + + + + 1 + + + 10000 + + + 10 + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 5 + + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + + + diff --git a/bacula/src/qt-console/tray-monitor/runjob.cpp b/bacula/src/qt-console/tray-monitor/runjob.cpp new file mode 100644 index 0000000000..e6204ee45b --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/runjob.cpp @@ -0,0 +1,515 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "runjob.h" +#include + +static void fillcombo(QComboBox *cb, alist *lst, bool addempty=true) +{ + if (lst && lst->size() > 0) { + QStringList list; + char *str; + if (addempty) { + list << QString(""); + } + foreach_alist(str, lst) { + list << QString(str); + } + cb->addItems(list); + } else { + cb->setEnabled(false); + } +} + +RunJob::RunJob(RESMON *r): QDialog(), res(r), tabAdvanced(NULL) +{ + int nbjob; + if (res->jobs->size() == 0) { + QMessageBox msgBox; + msgBox.setText(_("This restricted console does not have access to Backup jobs")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + deleteLater(); + return; + + } + + ui.setupUi(this); + setModal(true); + connect(ui.cancelButton, SIGNAL(clicked()), this, SLOT(close_cb())); + connect(ui.okButton, SIGNAL(clicked()), this, SLOT(runjob())); + connect(ui.jobCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(jobChanged(int))); + connect(ui.levelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(levelChanged(int))); + ui.dateTimeEdit->setMinimumDate(QDate::currentDate()); + ui.dateTimeEdit->setMaximumDate(QDate::currentDate().addDays(7)); + ui.dateTimeEdit->setDate(QDate::currentDate()); + ui.dateTimeEdit->setTime(QTime::currentTime()); + ui.boxEstimate->setVisible(false); + + res->mutex->lock(); + nbjob = res->jobs->size(); + fillcombo(ui.jobCombo, res->jobs, (nbjob > 1)); + fillcombo(ui.clientCombo, res->clients); + fillcombo(ui.filesetCombo,res->filesets); + fillcombo(ui.poolCombo, res->pools); + fillcombo(ui.storageCombo,res->storages); + fillcombo(ui.catalogCombo,res->catalogs); + res->mutex->unlock(); + connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChange(int))); + QStringList levels; + levels << "" << "Incremental" << "Differential" << "Full"; + ui.levelCombo->addItems(levels); + + MONITOR *m = (MONITOR*) GetNextRes(R_MONITOR, NULL); + if (!m->display_advanced_options) { + tabAdvanced = ui.tabWidget->widget(1); + ui.tabWidget->removeTab(1); + } + + show(); +}; + +void RunJob::tabChange(int idx) +{ + QString q = ui.tabWidget->tabText(idx); + if (q.contains("Advanced")) { + if (ui.jobCombo->currentText().compare("") == 0) { + pm_strcpy(curjob, ""); + ui.tab2->setEnabled(false); + + } else if (ui.jobCombo->currentText().compare(curjob.c_str()) != 0) { + task *t = new task(); + char *job = bstrdup(ui.jobCombo->currentText().toUtf8().data()); + pm_strcpy(curjob, job); // Keep the job name to not refresh the Advanced tab the next time + + Dmsg1(10, "get defaults for %s\n", job); + res->mutex->lock(); + bfree_and_null(res->defaults.job); + res->defaults.job = job; + res->mutex->unlock(); + + ui.tab2->setEnabled(false); + connect(t, SIGNAL(done(task *)), this, SLOT(fill_defaults(task *)), Qt::QueuedConnection); + t->init(res, TASK_DEFAULTS); + res->wrk->queue(t); + } + } +} + +void RunJob::runjob() +{ + POOL_MEM tmp; + char *p; + + p = ui.jobCombo->currentText().toUtf8().data(); + if (!p || !*p) { + QMessageBox msgBox; + msgBox.setText(_("Nothing selected")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + Mmsg(command, "run job=\"%s\" yes", p); + + if (strcmp(p, NPRTB(res->defaults.job)) == 0 || strcmp("", NPRTB(res->defaults.job)) == 0) { + p = ui.storageCombo->currentText().toUtf8().data(); + if (p && *p && strcmp(p, NPRTB(res->defaults.storage)) != 0) { + Mmsg(tmp, " storage=\"%s\"", p); + pm_strcat(command, tmp.c_str()); + } + + p = ui.clientCombo->currentText().toUtf8().data(); + if (p && *p && strcmp(p, NPRTB(res->defaults.client)) != 0) { + Mmsg(tmp, " client=\"%s\"", p); + pm_strcat(command, tmp.c_str()); + } + + p = ui.levelCombo->currentText().toUtf8().data(); + if (p && *p && strcmp(p, NPRTB(res->defaults.level)) != 0) { + Mmsg(tmp, " level=\"%s\"", p); + pm_strcat(command, tmp.c_str()); + } + + p = ui.poolCombo->currentText().toUtf8().data(); + if (p && *p && strcmp(p, NPRTB(res->defaults.pool)) != 0) { + Mmsg(tmp, " pool=\"%s\"", p); + pm_strcat(command, tmp.c_str()); + } + + p = ui.filesetCombo->currentText().toUtf8().data(); + if (p && *p && strcmp(p, NPRTB(res->defaults.fileset)) != 0) { + Mmsg(tmp, " fileset=\"%s\"", p); + pm_strcat(command, tmp.c_str()); + } + + if (res->defaults.priority && res->defaults.priority != ui.prioritySpin->value()) { + Mmsg(tmp, " priority=\"%d\"", res->defaults.priority); + pm_strcat(command, tmp.c_str()); + } + } + + QDate dnow = QDate::currentDate(); + QTime tnow = QTime::currentTime(); + QDate dval = ui.dateTimeEdit->date(); + QTime tval = ui.dateTimeEdit->time(); + + if (dval > dnow || (dval == dnow && tval > tnow)) { + Mmsg(tmp, " when=\"%s %s\"", dval.toString("yyyy-MM-dd").toUtf8().data(), tval.toString("hh:mm:00").toUtf8().data()); + pm_strcat(command, tmp.c_str()); + } + + if (res->type == R_CLIENT) { + pm_strcat(command, " fdcalled=1"); + } + + // Build the command and run it! + task *t = new task(); + connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection); + t->arg = command.c_str(); + t->init(res, TASK_RUN); + res->wrk->queue(t); +} + +void RunJob::jobStarted(task *t) +{ + Dmsg1(10, "%s\n", command.c_str()); + Dmsg1(10, "-> jobid=%d\n", t->result.i); + deleteLater(); + delete t; +} + +void RunJob::close_cb(task *t) +{ + deleteLater(); + delete t; +} + +void RunJob::close_cb() +{ + task *t = new task(); + connect(t, SIGNAL(done(task *)), this, SLOT(close_cb(task *)), Qt::QueuedConnection); + t->init(res, TASK_DISCONNECT); + res->wrk->queue(t); +} + +void RunJob::jobChanged(int) +{ + char *p; + ui.levelCombo->setCurrentIndex(0); + ui.storageCombo->setCurrentIndex(0); + ui.filesetCombo->setCurrentIndex(0); + ui.clientCombo->setCurrentIndex(0); + ui.storageCombo->setCurrentIndex(0); + ui.poolCombo->setCurrentIndex(0); + ui.catalogCombo->setCurrentIndex(0); + + p = ui.jobCombo->currentText().toUtf8().data(); + if (p && *p) { + task *t = new task(); + pm_strcpy(info, p); + connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection); + t->arg = info.c_str(); // Jobname + t->arg2 = NULL; // Level + t->init(res, TASK_INFO); + res->wrk->queue(t); + } +} + +void RunJob::levelChanged(int) +{ + char *p; + p = ui.jobCombo->currentText().toUtf8().data(); + if (p && *p) { + pm_strcpy(info, p); + p = ui.levelCombo->currentText().toUtf8().data(); + if (p && *p) { + task *t = new task(); + pm_strcpy(level, p); + connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection); + t->arg = info.c_str(); // Jobname + t->arg2 = level.c_str(); // Level + t->init(res, TASK_INFO); + res->wrk->queue(t); + } + } +} + +void RunJob::jobInfo(task *t) +{ + char ed1[50]; + res->mutex->lock(); + if (res->infos.CorrNbJob == 0) { + ui.boxEstimate->setVisible(false); + } else { + QString t; + edit_uint64_with_suffix(res->infos.JobBytes, ed1); + strncat(ed1, "B", sizeof(ed1)); + ui.labelJobBytes->setText(QString(ed1)); + ui.labelJobFiles->setText(QString(edit_uint64_with_commas(res->infos.JobFiles, ed1))); + ui.labelJobLevel->setText(QString(job_level_to_str(res->infos.JobLevel))); + 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); + ui.labelJobBytes_2->setToolTip(t); + 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); + ui.labelJobFiles_2->setToolTip(t); + ui.boxEstimate->setVisible(true); + } + res->mutex->unlock(); + t->deleteLater(); +} + +static void set_combo(QComboBox *dest, char *str) +{ + if (str) { + int idx = dest->findText(QString(str), Qt::MatchExactly); + if (idx >= 0) { + dest->setCurrentIndex(idx); + } + } +} + +void RunJob::fill_defaults(task *t) +{ + if (t->status == true) { + res->mutex->lock(); + set_combo(ui.levelCombo, res->defaults.level); + set_combo(ui.filesetCombo, res->defaults.fileset); + set_combo(ui.clientCombo, res->defaults.client); + set_combo(ui.storageCombo, res->defaults.storage); + set_combo(ui.poolCombo, res->defaults.pool); + set_combo(ui.catalogCombo, res->defaults.catalog); + res->mutex->unlock(); + } + + ui.tab2->setEnabled(true); + t->deleteLater(); +} + +RunJob::~RunJob() +{ + Dmsg0(10, "~RunJob()\n"); + if (tabAdvanced) { + delete tabAdvanced; + } +} + +void TSched::init(const char *cmd_dir) +{ + bool started = (timer >= 0); + if (started) { + stop(); + } + + bfree_and_null(command_dir); + command_dir = bstrdup(cmd_dir); + + if (started) { + start(); + } +} + +TSched::TSched() { + timer = -1; + command_dir = NULL; +} + +TSched::~TSched() { + if (timer >= 0) { + stop(); + } + bfree_and_null(command_dir); +} + +#ifndef HAVE_READDIR_R +int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); +#else +#include +#endif + +bool TSched::read_command_file(const char *file, alist *lst, btime_t mtime) +{ + POOLMEM *line; + bool ret=false; + char *p; + TSchedJob *s; + Dmsg1(50, "open command file %s\n", file); + FILE *fp = fopen(file, "r"); + if (!fp) { + return false; + } + line = get_pool_memory(PM_FNAME); + + /* Get the first line, client/component:command */ + while (bfgets(line, fp) != NULL) { + strip_trailing_junk(line); + Dmsg1(50, "%s\n", line); + if (line[0] == '#') { + continue; + } + + if ((p = strchr(line, ':')) != NULL) { + *p=0; + s = new TSchedJob(line, p+1, mtime); + lst->append(s); + ret = true; + } + } + + free_pool_memory(line); + fclose(fp); + return ret; +} + +#include "lib/plugins.h" +#include "lib/cmd_parser.h" + +void TSched::timerEvent(QTimerEvent *event) +{ + POOL_MEM tmp, command; + TSchedJob *j; + alist lst(10, not_owned_by_alist); + arg_parser parser; + int i; + task *t; + RESMON *res; + scan_for_commands(&lst); + + foreach_alist(j, (&lst)) { + if (parser.parse_cmd(j->command) == bRC_OK) { + if ((i = parser.find_arg_with_value("job")) > 0) { + QMessageBox msgbox; + foreach_res(res, R_CLIENT) { + if (strcmp(res->hdr.name, j->component) == 0) { + break; + } + } + if (!res) { + foreach_res(res, R_DIRECTOR) { + if (strcmp(res->hdr.name, j->component) == 0) { + break; + } + } + } + if (!res) { + msgbox.setIcon(QMessageBox::Information); + msgbox.setText(QString("Unable to find the component \"%1\" to run the job \"%2\".").arg(j->component, j->command)); + msgbox.setStandardButtons(QMessageBox::Ignore); + } else { + + msgbox.setIcon(QMessageBox::Information); + msgbox.setText(QString("The job \"%1\" will start automatically in few seconds...").arg(parser.argv[i])); + msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore); + msgbox.setDefaultButton(QMessageBox::Ok); + msgbox.button(QMessageBox::Ok)->animateClick(6000); + } + switch(msgbox.exec()) { + case QMessageBox::Ok: + Mmsg(command, "%s yes", j->command); + + if (res->type == R_CLIENT) { + pm_strcat(command, " fdcalled=1"); + } + + // Build the command and run it! + t = new task(); + connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection); + t->arg = command.c_str(); + t->init(res, TASK_RUN); + res->wrk->queue(t); + + break; + case QMessageBox::Cancel: + case QMessageBox::Ignore: + break; + } + } + } + delete j; + } +} + +void TSched::jobStarted(task *t) +{ + Dmsg1(10, "-> jobid=%d\n", t->result.i); + t->deleteLater(); +} + + +bool TSched::scan_for_commands(alist *commands) +{ + int name_max, len; + DIR* dp = NULL; + POOL_MEM fname(PM_FNAME), fname2(PM_FNAME); + bool ret=false, found=false; + struct dirent *entry = NULL, *result; + struct stat statp; + + name_max = pathconf(".", _PC_NAME_MAX); + if (name_max < 1024) { + name_max = 1024; + } + + if (!(dp = opendir(command_dir))) { + berrno be; + Dmsg2(0, "Failed to open directory %s: ERR=%s\n", + command_dir, be.bstrerror()); + goto bail_out; + } + + entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000); + for ( ;; ) { + if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) { + if (!found) { + goto bail_out; + } + break; + } + if (strcmp(result->d_name, ".") == 0 || + strcmp(result->d_name, "..") == 0) { + continue; + } + len = strlen(result->d_name); + if (len <= 5) { + continue; + } + if (strcmp(result->d_name + len - 5, ".bcmd") != 0) { + continue; + } + + Mmsg(fname, "%s/%s", command_dir, result->d_name); + + if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) { + continue; /* ignore directories & special files */ + } + + if (read_command_file(fname.c_str(), commands, statp.st_mtime)) { + Mmsg(fname2, "%s.ok", fname.c_str()); + unlink(fname2.c_str()); + rename(fname.c_str(), fname2.c_str()); // TODO: We should probably unlink the file + } + } +bail_out: + if (entry) { + free(entry); + } + if (dp) { + closedir(dp); + } + return ret; +} diff --git a/bacula/src/qt-console/tray-monitor/runjob.h b/bacula/src/qt-console/tray-monitor/runjob.h new file mode 100644 index 0000000000..6333865bdf --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/runjob.h @@ -0,0 +1,118 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef RUN_H +#define RUN_H + +#include "common.h" +#include "ui_run.h" +#include "tray_conf.h" +#include "task.h" + +class RunJob: public QDialog +{ + Q_OBJECT + +public: + RESMON *res; + QWidget *tabAdvanced; + POOL_MEM command; + POOL_MEM info; + POOL_MEM level; + POOL_MEM curjob; + Ui::runForm ui; + RunJob(RESMON *r); + ~RunJob(); + +public slots: + void jobChanged(int); + void levelChanged(int); + void jobStarted(task *); + void jobInfo(task *); + void fill_defaults(task *); + void tabChange(int idx); + void runjob(); + /* close the window properly */ + void close_cb(task *t); + void close_cb(); +}; + +/* Object that can scan a directory to find jobs */ +class TSched: public QObject +{ + Q_OBJECT +private: + char *command_dir; + bool read_command_file(const char *file, alist *lst, btime_t mtime); + int timer; + +public: + TSched(); + ~TSched(); + void init(const char *cmd_dir); + bool scan_for_commands(alist *lst); + void start() { + timer = startTimer(60000); // 1-minute timer + }; + void stop() { + if (timer >= 0) { + killTimer(timer); + timer = -1; + } + }; +public slots: + void jobStarted(task *t); +protected: + void timerEvent(QTimerEvent *event); + +}; + + +/* Job found in the command directory */ +class TSchedJob: public QObject +{ + Q_OBJECT + +public: + char *component; // Name of the daemon + char *command; // job command + btime_t create_date; // When the command file was created + TSchedJob() : component(NULL), command(NULL) {}; + + TSchedJob(const char *comp, const char *cmd, btime_t cd) { + component = bstrdup(comp); + command = bstrdup(cmd); + create_date = cd; + }; + + ~TSchedJob() { + clear(); + }; + void clear() { + if (component) { + bfree_and_null(component); + } + if (command) { + bfree_and_null(command); + } + create_date = 0; + }; +}; + +#endif diff --git a/bacula/src/qt-console/tray-monitor/sd-monitor.ui b/bacula/src/qt-console/tray-monitor/sd-monitor.ui new file mode 100644 index 0000000000..3c00e6c9f7 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/sd-monitor.ui @@ -0,0 +1,162 @@ + + + sdStatus + + + + 0 + 0 + 518 + 435 + + + + Form + + + + + + + + Storage Daemon Status + + + + + + + + + + + + + Name: + + + + + + + + + + + + + + Started: + + + + + + + + + + + + + + + + + + + + + Version: + + + + + + + Plugins: + + + + + + + + + + Running Jobs + + + + + + QAbstractItemView::SingleSelection + + + false + + + + + + + + + + Terminated Jobs + + + + + + QAbstractItemView::SingleSelection + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + :/images/view-refresh.png:/images/view-refresh.png + + + + + + + + + + + + diff --git a/bacula/src/qt-console/tray-monitor/sdstatus.cpp b/bacula/src/qt-console/tray-monitor/sdstatus.cpp new file mode 100644 index 0000000000..741305afbe --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/sdstatus.cpp @@ -0,0 +1,125 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "sdstatus.h" +#include "../util/fmtwidgetitem.h" +#include "jcr.h" + +void SDStatus::doUpdate() +{ + if (count == 0) { + count++; + task *t = new task(); + status.pushButton->setEnabled(false); + connect(t, SIGNAL(done(task *)), this, SLOT(taskDone(task *)), Qt::QueuedConnection); + t->init(res, TASK_STATUS); + res->wrk->queue(t); + status.statusBar->setText(QString("Trying to connect to Storage...")); + Dmsg1(50, "doUpdate(%p)\n", res); + } +} + +void SDStatus::taskDone(task *t) +{ + count--; + if (!t->status) { + status.statusBar->setText(QString(t->errmsg)); + + } else { + status.statusBar->clear(); + if (t->type == TASK_STATUS) { + char ed1[50]; + struct s_last_job *ljob; + struct s_running_job *rjob; + res->mutex->lock(); + status.labelName->setText(QString(res->name)); + status.labelVersion->setText(QString(res->version)); + status.labelStarted->setText(QString(res->started)); + status.labelPlugins->setText(QString(res->plugins)); + /* Clear the table first */ + Freeze(*status.tableRunning); + Freeze(*status.tableTerminated); + QStringList headerlistR = (QStringList() << tr("JobId") + << tr("Job") << tr("Level") << tr("Client") + << tr("Storage") + << tr("Files") << tr("Bytes") << tr("Errors")); + status.tableRunning->clear(); + status.tableRunning->setRowCount(0); + status.tableRunning->setColumnCount(headerlistR.count()); + status.tableRunning->setHorizontalHeaderLabels(headerlistR); + status.tableRunning->setEditTriggers(QAbstractItemView::NoEditTriggers); + status.tableRunning->verticalHeader()->hide(); + status.tableRunning->setSortingEnabled(true); + + if (res->running_jobs) { + status.tableRunning->setRowCount(res->running_jobs->size()); + int row=0; + foreach_alist(rjob, res->running_jobs) { + int col=0; + TableItemFormatter item(*status.tableRunning, row++); + item.setNumericFld(col++, QString(edit_uint64(rjob->JobId, ed1))); + item.setTextFld(col++, QString(rjob->Job)); + item.setJobLevelFld(col++, QString(rjob->JobLevel)); + item.setTextFld(col++, QString(rjob->Client)); + item.setTextFld(col++, QString(rjob->Storage)); + item.setNumericFld(col++, QString(edit_uint64(rjob->JobFiles, ed1))); + item.setBytesFld(col++, QString(edit_uint64(rjob->JobBytes, ed1))); + item.setNumericFld(col++, QString(edit_uint64(rjob->Errors, ed1))); + } + } else { + Dmsg0(0, "Strange, the list is NULL\n"); + } + + QStringList headerlistT = (QStringList() << tr("JobId") + << tr("Job") << tr("Level") + << tr("Status") << tr("Files") << tr("Bytes") + << tr("Errors")); + + status.tableTerminated->clear(); + status.tableTerminated->setRowCount(0); + status.tableTerminated->setColumnCount(headerlistT.count()); + status.tableTerminated->setHorizontalHeaderLabels(headerlistT); + status.tableTerminated->setEditTriggers(QAbstractItemView::NoEditTriggers); + status.tableTerminated->verticalHeader()->hide(); + status.tableTerminated->setSortingEnabled(true); + + if (res->terminated_jobs) { + status.tableTerminated->setRowCount(res->terminated_jobs->size()); + int row=0; + foreach_dlist(ljob, res->terminated_jobs) { + int col=0; + TableItemFormatter item(*status.tableTerminated, row++); + item.setNumericFld(col++, QString(edit_uint64(ljob->JobId, ed1))); + item.setTextFld(col++, QString(ljob->Job)); + item.setJobLevelFld(col++, QString(ljob->JobLevel)); + item.setJobStatusFld(col++, QString(ljob->JobStatus)); + item.setNumericFld(col++, QString(edit_uint64(ljob->JobFiles, ed1))); + item.setBytesFld(col++, QString(edit_uint64(ljob->JobBytes, ed1))); + item.setNumericFld(col++, QString(edit_uint64(ljob->Errors, ed1))); + } + } else { + Dmsg0(0, "Strange, the list is NULL\n"); + } + res->mutex->unlock(); + } + Dmsg1(50, " Task %p OK\n", t); + } + t->deleteLater(); + status.pushButton->setEnabled(true); +} diff --git a/bacula/src/qt-console/tray-monitor/sdstatus.h b/bacula/src/qt-console/tray-monitor/sdstatus.h new file mode 100644 index 0000000000..17f0a3fd41 --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/sdstatus.h @@ -0,0 +1,42 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "common.h" +#include "ui_sd-monitor.h" +#include "task.h" +#include "status.h" + +class SDStatus: public ResStatus +{ + Q_OBJECT + +public: + Ui::sdStatus status; + + SDStatus(RESMON *d): ResStatus(d) + { + status.setupUi(this); + QObject::connect(status.pushButton, SIGNAL(clicked()), this, SLOT(doUpdate()), Qt::QueuedConnection); + }; + ~SDStatus() { + }; +public slots: + void doUpdate(); + void taskDone(task *); +}; diff --git a/bacula/src/qt-console/tray-monitor/status.cpp b/bacula/src/qt-console/tray-monitor/status.cpp new file mode 100644 index 0000000000..8320c5b22e --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/status.cpp @@ -0,0 +1,42 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "status.h" +#include "lib/worker.h" + +void ResStatus::doUpdate() +{ + if (count == 0) { + task *t = new task(); + connect(t, SIGNAL(done(task *)), this, SLOT(taskDone(task *)), Qt::QueuedConnection); + t->init(res, TASK_STATUS); + res->wrk->queue(t); + Dmsg0(0, "doUpdate()\n"); + count++; + } +} + +void ResStatus::taskDone(task *t) +{ + if (!t->status) { + Dmsg2(0, " Task %p failed => %s\n", t, t->errmsg); + } + delete t; + count--; +} diff --git a/bacula/src/qt-console/tray-monitor/status.h b/bacula/src/qt-console/tray-monitor/status.h new file mode 100644 index 0000000000..0e0f4ea98e --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/status.h @@ -0,0 +1,44 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef STATUS_H +#define STATUS_H + +#include "common.h" +#include +#include "tray_conf.h" +#include "task.h" + +class ResStatus: public QWidget +{ + Q_OBJECT + +public: + int count; + RESMON *res; + ResStatus(RESMON *c): count(0), res(c) { + }; + virtual ~ResStatus() { + }; +public slots: + virtual void doUpdate(); + virtual void taskDone(task *t); +}; + +#endif diff --git a/bacula/src/qt-console/tray-monitor/task.cpp b/bacula/src/qt-console/tray-monitor/task.cpp new file mode 100644 index 0000000000..911ba2f3df --- /dev/null +++ b/bacula/src/qt-console/tray-monitor/task.cpp @@ -0,0 +1,951 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2017 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "task.h" +#include "jcr.h" +#define dbglvl 10 +int authenticate_daemon(JCR *jcr, MONITOR *monitor, RESMON *res); + +static void *handle_task(void *data) +{ + task *t; + worker *wrk = (worker *)data; + lmgr_init_thread(); + + wrk->set_running(); + Dmsg0(dbglvl, "Worker started\n"); + + while (!wrk->is_quit_state()) { + if (wrk->is_wait_state()) { + wrk->wait(); + continue; + } + t = (task *)wrk->dequeue(); + if (!t) { + continue; + } + /* Do the work */ + switch(t->type) { + case TASK_STATUS: + t->do_status(); + break; + case TASK_RESOURCES: + t->get_resources(); + break; + case TASK_DEFAULTS: + t->get_job_defaults(); + break; + case TASK_RUN: + t->run_job(); + break; + case TASK_BWLIMIT: + t->set_bandwidth(); + break; + case TASK_INFO: + t->get_job_info(t->arg2); + break; + case TASK_DISCONNECT: + t->disconnect_bacula(); + t->mark_as_done(); + break; + default: + Mmsg(t->errmsg, "Unknown task"); + t->mark_as_failed(); + break; + } + } + Dmsg0(dbglvl, "Worker stoped\n"); + lmgr_cleanup_thread(); + return NULL; +} + +bool task::set_bandwidth() +{ + bool ret = false; + btimer_t *tid = NULL; + if (res->type != R_CLIENT) { + mark_as_failed(); + Mmsg(errmsg, _("Bandwidth can set only set on Client")); + return false; + } + if (!arg || !*arg) { + mark_as_failed(); + Mmsg(errmsg, _("Bandwidth parameter is invalid")); + return false; + } + + if (res->proxy_sent) { + free_bsock(res->bs); + } + + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + mark_as_failed(); + return false; + } + } + + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + res->bs->fsend("setbandwidth limit=%s\n", NPRTB(arg)); + while (get_next_line(res)) { + Dmsg1(dbglvl, "-> %s\n", curline); + } + + if (tid) { + stop_thread_timer(tid); + } + + /* Do not reuse the same socket */ + disconnect_bacula(); + + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + return ret; +} + +RESMON *task::get_res() +{ + return res; +} + +void task::lock_res() +{ + res->mutex->lock(); +} + +void task::unlock_res() +{ + res->mutex->unlock(); +} + +bool task::disconnect_bacula() +{ + free_bsock(res->bs); + return true; +} + +bool task::connect_bacula() +{ + JCR jcr; + bool ret = false; + memset(&jcr, 0, sizeof(jcr)); + curend = curline = NULL; + + RESMON *r = get_res(); + MONITOR *monitor = (MONITOR*)GetNextRes(R_MONITOR, NULL); + + if (r->type == R_CLIENT) { + r->proxy_sent = false; + if (r->bs && (r->bs->is_error() || !r->bs->is_open())) { + free_bsock(r->bs); + } + if (!r->bs) { + r->bs = new_bsock(); + Dmsg0(dbglvl, "Trying to connect to FD\n"); + if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Client daemon"), + r->address, NULL, r->port, 0)) + { + Dmsg0(dbglvl, "Connect done!\n"); + jcr.file_bsock = r->bs; + if (!authenticate_daemon(&jcr, monitor, r)) { + Dmsg0(dbglvl, "Unable to authenticate\n"); + Mmsg(errmsg, "Unable to authenticate with the FileDaemon"); + free_bsock(r->bs); + return false; + } + Dmsg0(dbglvl, "Authenticate OK\n"); + ret = true; + } else { + Mmsg(errmsg, "Unable to connect to the FileDaemon"); + Dmsg0(dbglvl, "Connect error!\n"); + } + } else { + ret = true; + } + } + if (r->type == R_STORAGE) { + if (r->bs && (r->bs->is_error() || !r->bs->is_open())) { + free_bsock(r->bs); + } + if (!r->bs) { + r->bs = new_bsock(); + Dmsg0(dbglvl, "Trying to connect to FD\n"); + if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Storage daemon"), + r->address, NULL, r->port, 0)) + { + Dmsg0(dbglvl, "Connect done!\n"); + jcr.store_bsock = r->bs; + if (!authenticate_daemon(&jcr, monitor, r)) { + Dmsg0(dbglvl, "Unable to authenticate\n"); + Mmsg(errmsg, "Unable to authenticate with the Storage Daemon"); + free_bsock(r->bs); + return false; + } + Dmsg0(dbglvl, "Authenticate OK\n"); + ret = true; + } else { + Mmsg(errmsg, "Unable to connect to the Storage Daemon"); + Dmsg0(dbglvl, "Connect error!\n"); + } + } else { + ret = true; + } + } + if (r->type == R_DIRECTOR) { + if (r->bs && (r->bs->is_error() || !r->bs->is_open())) { + free_bsock(r->bs); + } + if (!r->bs) { + r->bs = new_bsock(); + Dmsg0(dbglvl, "Trying to connect to DIR\n"); + if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Director daemon"), + r->address, NULL, r->port, 0)) + { + Dmsg0(dbglvl, "Connect done!\n"); + jcr.dir_bsock = r->bs; + if (!authenticate_daemon(&jcr, monitor, r)) { + Dmsg0(dbglvl, "Unable to authenticate\n"); + Mmsg(errmsg, "Unable to authenticate with the Director"); + free_bsock(r->bs); + return false; + } + Dmsg0(dbglvl, "Authenticate OK\n"); + ret = true; + } else { + Mmsg(errmsg, "Unable to connect to the Director"); + Dmsg0(dbglvl, "Connect error!\n"); + } + } else { + ret = true; + } + } + return ret; +} + +bool task::read_status_running(RESMON *r) +{ + bool ret = false; + char *start, *end; + struct s_running_job *item = NULL; + alist *running_jobs = New(alist(10, owned_by_alist)); + + while (r->bs->recv() >= -1) { + if (r->bs->msglen < 0 && + r->bs->msglen != BNET_CMD_BEGIN && + r->bs->msglen != BNET_CMD_OK) + { + Dmsg1(dbglvl, "Got Signal %s\n", bnet_sig_to_ascii(r->bs->msglen)); + break; + } + Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen); + start = r->bs->msg; + + while ((end = strchr(start, '\n')) != NULL) { + *end = 0; + Dmsg1(dbglvl, "line=[%s]\n", start); + if (strncasecmp(start, "jobid=", 6) == 0) { + if (item) { + Dmsg1(dbglvl, "Append item %ld\n", item->JobId); + running_jobs->append(item); + } + item = (struct s_running_job *)malloc(sizeof(struct s_running_job)); + memset(item, 0, sizeof(struct s_running_job)); + item->JobId = str_to_uint64(start + 6); + + } else if (!item) { + Dmsg0(dbglvl, "discard line\n"); + + } else if (strncasecmp(start, "level=", 6) == 0) { + item->JobLevel = start[6]; + + } else if (strncasecmp(start, "type=", 5) == 0) { + item->JobType = start[5]; + + } else if (strncasecmp(start, "status=", 7) == 0) { + item->JobStatus = start[7]; + + } else if (strncasecmp(start, "jobbytes=", 9) == 0) { + item->JobBytes = str_to_uint64(start + 9); + + } else if (strncasecmp(start, "jobfiles=", 9) == 0) { + item->JobFiles = str_to_uint64(start + 9); + + } else if (strncasecmp(start, "job=", 4) == 0) { + bstrncpy(item->Job, start + 4, sizeof(item->Job)); + + } else if (strncasecmp(start, "starttime_epoch=", 16) == 0) { + item->start_time = str_to_uint64(start + 16); + + } else if (strncasecmp(start, "schedtime_epoch=", 16) == 0) { + item->sched_time = str_to_uint64(start + 16); + + } else if (strncasecmp(start, "bytes/sec=", 10) == 0) { + item->bytespersec = str_to_uint64(start + 10); + + } else if (strncasecmp(start, "avebytes_sec=", 13) == 0) { + item->bytespersec = str_to_uint64(start + 13); + + } else if (strncasecmp(start, "errors=", 7) == 0) { + item->Errors = str_to_uint64(start + 7); + + } else if (strncasecmp(start, "readbytes=", 10) == 0) { + item->ReadBytes = str_to_uint64(start + 10); + + } else if (strncasecmp(start, "processing file=", 16) == 0) { + bstrncpy(item->CurrentFile, start + 16, sizeof(item->CurrentFile)); + + } else if (strncasecmp(start, "clientname=", 11) == 0) { + bstrncpy(item->Client, start + 11, sizeof(item->Client)); + + } else if (strncasecmp(start, "fileset=", 8) == 0) { + bstrncpy(item->FileSet, start + 8, sizeof(item->FileSet)); + + } else if (strncasecmp(start, "storage=", 8) == 0) { + bstrncpy(item->Storage, start + 8, sizeof(item->Storage)); + + } else if (strncasecmp(start, "rstorage=", 8) == 0) { + bstrncpy(item->RStorage, start + 8, sizeof(item->Storage)); + + } else if (strncasecmp(start, "sdtls=", 6) == 0) { + item->SDtls = str_to_uint64(start + 6); + } + start = end+1; + } + r->last_update = time(NULL); + + if (r->bs->is_error()) { + Mmsg(errmsg, "Got error on the socket communication line"); + goto bail_out; + } + } + if (item) { + Dmsg1(dbglvl, "Append item %ld\n", item->JobId); + running_jobs->append(item); + } + ret = true; + +bail_out: + r->mutex->lock(); + if (r->running_jobs) { + delete r->running_jobs; + } + r->running_jobs = running_jobs; + r->mutex->unlock(); + + return ret; +} + +bool task::read_status_terminated(RESMON *r) +{ + bool ret = false; + char *start, *end; + struct s_last_job *item = NULL; + + r->mutex->lock(); + if (r->terminated_jobs) { + delete r->terminated_jobs; + } + r->terminated_jobs = New(dlist(item, &item->link)); + r->mutex->unlock(); + + while (r->bs->recv() >= -1) { + if (r->bs->msglen < 0 && + r->bs->msglen != BNET_CMD_BEGIN && + r->bs->msglen != BNET_CMD_OK) + { + Dmsg1(dbglvl, "Got Signal %s\n", bnet_sig_to_ascii(r->bs->msglen)); + break; + } + + Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen); + r->mutex->lock(); + start = r->bs->msg; + + while ((end = strchr(start, '\n')) != NULL) { + *end = 0; + Dmsg1(dbglvl, "line=[%s]\n", start); + if (strncasecmp(start, "jobid=", 6) == 0) { + if (item) { + Dmsg1(dbglvl, "Append item %ld\n", item->JobId); + r->terminated_jobs->append(item); + } + item = (struct s_last_job *)malloc(sizeof(struct s_last_job)); + memset(item, 0, sizeof(struct s_last_job)); + item->JobId = str_to_uint64(start + 6); + + } else if (!item) { + Dmsg0(dbglvl, "discard line\n"); + + } else if (strncasecmp(start, "level=", 6) == 0) { + item->JobLevel = start[6]; + + } else if (strncasecmp(start, "type=", 5) == 0) { + item->JobType = start[5]; + + } else if (strncasecmp(start, "status=", 7) == 0) { + item->JobStatus = start[7]; + + } else if (strncasecmp(start, "jobbytes=", 9) == 0) { + item->JobBytes = str_to_uint64(start + 9); + + } else if (strncasecmp(start, "jobfiles=", 9) == 0) { + item->JobFiles = str_to_uint64(start + 9); + + } else if (strncasecmp(start, "job=", 4) == 0) { + bstrncpy(item->Job, start + 4, sizeof(item->Job)); + + } else if (strncasecmp(start, "starttime_epoch=", 16) == 0) { + item->start_time = str_to_uint64(start + 16); + + } else if (strncasecmp(start, "endtime_epoch=", 14) == 0) { + item->end_time = str_to_uint64(start + 14); + + } else if (strncasecmp(start, "errors=", 7) == 0) { + item->Errors = str_to_uint64(start + 7); + } + start = end+1; + } + r->last_update = time(NULL); + r->mutex->unlock(); + + if (r->bs->is_error()) { + Mmsg(errmsg, "Got error on the socket communication line"); + goto bail_out; + } + } + if (item) { + r->mutex->lock(); + Dmsg1(dbglvl, "Append item %ld\n", item->JobId); + r->terminated_jobs->append(item); + r->mutex->unlock(); + } + ret = true; + +bail_out: + return ret; +} + +bool task::read_status_header(RESMON *r) +{ + bool ret = false; + char *start, *end; + + while (r->bs->recv() >= -1) { + if (r->bs->msglen < 0 && + r->bs->msglen != BNET_CMD_BEGIN && + r->bs->msglen != BNET_CMD_OK) + { + Dmsg1(dbglvl, "Got Signal %d\n", r->bs->msglen); + break; + } + + Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen); + r->mutex->lock(); + start = r->bs->msg; + + while ((end = strchr(start, '\n')) != NULL) { + *end = 0; + Dmsg1(dbglvl, "line=[%s]\n", start); + if (strncasecmp(start, "name=", 5) == 0) { + bstrncpy(r->name, start + 5, sizeof(r->name)); + + } else if (strncasecmp(start, "version=", 8) == 0) { + bstrncpy(r->version, start + 8, sizeof(r->version)); + + } else if (strncasecmp(start, "plugins=", 8) == 0) { + bstrncpy(r->plugins, start + 8, sizeof(r->plugins)); + + } else if (strncasecmp(start, "bwlimit=", 8) == 0) { + r->bwlimit = str_to_uint64(start + 8); + + } else if (strncasecmp(start, "started=", 8) == 0) { + bstrncpy(r->started, start + 8, sizeof(r->started)); + + } else if (strncasecmp(start, "reloaded=", 9) == 0) { + bstrncpy(r->reloaded, start + 9, sizeof(r->reloaded)); + } + start = end+1; + } + + if (r->bs->is_error()) { + r->mutex->unlock(); + Mmsg(errmsg, "Got error on the socket communication line"); + goto bail_out; + + } + r->last_update = time(NULL); + r->mutex->unlock(); + } + ret = true; +bail_out: + return ret; +} + + +bool task::do_status() +{ + bool ret = false; + btimer_t *tid = NULL; + + /* We don't want to use a proxy session */ + if (res->type == R_CLIENT && res->proxy_sent) { + free_bsock(res->bs); + } + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + goto bail_out; + } + } + /* TODO: */ + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + if (res->type == R_CLIENT || res->type == R_STORAGE) { + Dmsg0(dbglvl, "Send status command header\n"); + res->bs->fsend(".status header api=2\n"); + // TODO: Update a local set of variables and commit everything when it's done + ret = read_status_header(res); + + if (ret) { + res->bs->fsend(".status terminated api=2\n"); + ret = read_status_terminated(res); + } + if (ret) { + res->bs->fsend(".status running api=2\n"); + ret = read_status_running(res); + } + } + if (res->type == R_DIRECTOR) { + Dmsg0(dbglvl, "-> .api 2\n"); + res->bs->fsend(".api 2\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + Dmsg0(dbglvl, "Send status command header\n"); + res->bs->fsend(".status dir header\n"); + // TODO: Update a local set of variables and commit everything when it's done + ret = read_status_header(res); + + if (ret) { + Dmsg0(dbglvl, "Send status command terminated\n"); + res->bs->fsend(".status dir terminated\n"); + ret = read_status_terminated(res); + } + if (ret) { + Dmsg0(dbglvl, "Send status command running\n"); + res->bs->fsend(".status dir running\n"); + ret = read_status_running(res); + } + } +bail_out: + if (tid) { + stop_thread_timer(tid); + } + /* Use a new socket the next time */ + disconnect_bacula(); + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + return ret; +} + +bool task::get_next_line(RESMON *r) +{ + /* We are currently reading a line */ + if (curline && curend && r->bs->msglen > 0 && curend < (r->bs->msg + r->bs->msglen - 1)) { + curline = curend + 1; /* skip \0 */ + if ((curend = strchr(curline, '\n')) != NULL) { + *curend = '\0'; + } + return true; + } + curline = curend = NULL; + do { + r->bs->recv(); + + if (r->bs->msglen < 0) { + Dmsg1(dbglvl, "<- %s\n", bnet_sig_to_ascii(r->bs->msglen)); + switch(r->bs->msglen) { + case BNET_ERROR_MSG: + r->bs->recv(); + strip_trailing_junk(r->bs->msg); + Dmsg1(0, "ERROR: %s\n", r->bs->msg); + break; + case BNET_MAIN_PROMPT: // stop + return false; + case BNET_CMD_OK: + case BNET_CMD_BEGIN: + case BNET_MSGS_PENDING: + break; + case BNET_TERMINATE: + return false; + default: // error or question? + return false; + } + + } else if (r->bs->msglen == 0) { // strange + return false; + + } else { + Dmsg1(10, "<- %s\n", r->bs->msg); + curline = r->bs->msg; + curend = strchr(curline, '\n'); + if (curend) { + *curend = 0; + } + return true; // something to read + } + } while (!r->bs->is_error()); + return false; +} + +bool task::get_job_defaults() +{ + bool ret = false; + btimer_t *tid = NULL; + char *p; + + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + goto bail_out; + } + } + + res->mutex->lock(); + bfree_and_null(res->defaults.client); + bfree_and_null(res->defaults.pool); + bfree_and_null(res->defaults.storage); + bfree_and_null(res->defaults.level); + bfree_and_null(res->defaults.type); + bfree_and_null(res->defaults.fileset); + bfree_and_null(res->defaults.catalog); + + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + if (res->type == R_CLIENT && !res->proxy_sent) { + res->proxy_sent = true; + res->bs->fsend("proxy\n"); + while (get_next_line(res)) { + if (strncmp(curline, "2000", 4) != 0) { + pm_strcpy(errmsg, curline); + goto bail_out; + } + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + } + + res->bs->fsend(".api 2\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + res->bs->fsend(".defaults job=\"%s\"\n", res->defaults.job); + while (get_next_line(res)) { + Dmsg1(dbglvl, "line = [%s]\n", curline); + if ((p = strchr(curline, '=')) == NULL) { + continue; + } + *p++ = 0; + if (strcasecmp(curline, "client") == 0) { + res->defaults.client = bstrdup(p); + + } else if (strcasecmp(curline, "pool") == 0) { + res->defaults.pool = bstrdup(p); + + } else if (strcasecmp(curline, "storage") == 0) { + res->defaults.storage = bstrdup(p); + + } else if (strcasecmp(curline, "level") == 0) { + res->defaults.level = bstrdup(p); + + } else if (strcasecmp(curline, "type") == 0) { + res->defaults.type = bstrdup(p); + + } else if (strcasecmp(curline, "fileset") == 0) { + res->defaults.fileset = bstrdup(p); + + } else if (strcasecmp(curline, "catalog") == 0) { + res->defaults.catalog = bstrdup(p); + + } else if (strcasecmp(curline, "priority") == 0) { + res->defaults.priority = str_to_uint64(p); + } + } + ret = true; +bail_out: + if (tid) { + stop_thread_timer(tid); + } + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + res->mutex->unlock(); + return ret; +} + +bool task::get_job_info(const char *level) +{ + bool ret = false; + btimer_t *tid = NULL; + char *p; + + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + goto bail_out; + } + } + res->mutex->lock(); + memset(&res->infos, 0, sizeof(res->infos)); + + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + if (res->type == R_CLIENT && !res->proxy_sent) { + res->proxy_sent = true; + res->bs->fsend("proxy\n"); + while (get_next_line(res)) { + if (strncmp(curline, "2000", 4) != 0) { + pm_strcpy(errmsg, curline); + goto bail_out; + } + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + } + + res->bs->fsend(".api 2\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + if (level) { + res->bs->fsend(".estimate job=\"%s\" level=%s\n", arg, level); + } else { + res->bs->fsend(".estimate job=\"%s\"\n", arg); + } + while (get_next_line(res)) { + Dmsg1(dbglvl, "line = [%s]\n", curline); + if ((p = strchr(curline, '=')) == NULL) { + continue; + } + *p++ = 0; + if (strcasecmp(curline, "level") == 0) { + res->infos.JobLevel = p[0]; + + } else if (strcasecmp(curline, "jobbytes") == 0) { + res->infos.JobBytes = str_to_uint64(p); + + } else if (strcasecmp(curline, "jobfiles") == 0) { + res->infos.JobFiles = str_to_uint64(p); + + } else if (strcasecmp(curline, "corrbytes") == 0) { + res->infos.CorrJobBytes = str_to_uint64(p); + + } else if (strcasecmp(curline, "corrfiles") == 0) { + res->infos.CorrJobFiles = str_to_uint64(p); + + } else if (strcasecmp(curline, "nbjob") == 0) { + res->infos.CorrNbJob = str_to_uint64(p); + } + } + ret = true; +bail_out: + res->mutex->unlock(); + if (tid) { + stop_thread_timer(tid); + } + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + return ret; +} + +bool task::run_job() +{ + bool ret = false; + char *p; + btimer_t *tid = NULL; + + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + goto bail_out; + } + } + + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + if (res->type == R_CLIENT && !res->proxy_sent) { + res->proxy_sent = true; + res->bs->fsend("proxy\n"); + while (get_next_line(res)) { + if (strncmp(curline, "2000", 4) != 0) { + pm_strcpy(errmsg, curline); + goto bail_out; + } + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + } + + res->bs->fsend(".api 2\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + if (res->type == R_DIRECTOR && res->use_setip) { + res->bs->fsend("setip\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + } + res->bs->fsend("%s\n", arg); + while (get_next_line(res)) { + if ((p = strstr(curline, "JobId=")) != NULL && sscanf(p, "JobId=%d\n", &result.i) == 1) { + ret = true; + } + } + // Close the socket, it's over or we don't want to reuse it + disconnect_bacula(); + +bail_out: + + if (tid) { + stop_thread_timer(tid); + } + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + return ret; +} + +/* Get resources to run a job */ +bool task::get_resources() +{ + bool ret = false; + btimer_t *tid = NULL; + + if (!res->bs || !res->bs->is_open() || res->bs->is_error()) { + if (!connect_bacula()) { + goto bail_out; + } + } + + res->mutex->lock(); + if (res->jobs) { + delete res->jobs; + } + res->jobs = New(alist(10, owned_by_alist)); + if (res->clients) { + delete res->clients; + } + res->clients = New(alist(10, owned_by_alist)); + if (res->filesets) { + delete res->filesets; + } + res->filesets = New(alist(10, owned_by_alist)); + if (res->pools) { + delete res->pools; + } + res->pools = New(alist(10, owned_by_alist)); + if (res->storages) { + delete res->storages; + } + res->storages = New(alist(10, owned_by_alist)); + if (res->catalogs) { + delete res->catalogs; + } + res->catalogs = New(alist(10, owned_by_alist)); + + tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120); + if (res->type == R_CLIENT && !res->proxy_sent) { + res->proxy_sent = true; + res->bs->fsend("proxy\n"); + while (get_next_line(res)) { + if (strncmp(curline, "2000", 4) != 0) { + pm_strcpy(errmsg, curline); + goto bail_out; + } + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + + res->bs->fsend(".api 2\n"); + while (get_next_line(res)) { + Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline); + } + + res->bs->fsend(".jobs type=B\n"); + while (get_next_line(res)) { + res->jobs->append(bstrdup(curline)); + } + + res->bs->fsend(".pools\n"); + while (get_next_line(res)) { + res->pools->append(bstrdup(curline)); + } + + res->bs->fsend(".clients\n"); + while (get_next_line(res)) { + res->clients->append(bstrdup(curline)); + } + + res->bs->fsend(".filesets\n"); + while (get_next_line(res)) { + res->filesets->append(bstrdup(curline)); + } + + res->bs->fsend(".storage\n"); + while (get_next_line(res)) { + res->storages->append(bstrdup(curline)); + } + + res->bs->fsend(".catalog\n"); + while (get_next_line(res)) { + res->catalogs->append(bstrdup(curline)); + } + +bail_out: + res->mutex->unlock(); + + if (tid) { + stop_thread_timer(tid); + } + if (ret) { + mark_as_done(); + } else { + mark_as_failed(); + } + return ret; +} + +worker *worker_start() +{ + worker *w = New(worker()); + w->start(handle_task, w); + return w; +} + +void worker_stop(worker *w) +{ + if (w) { + w->stop(); + delete w; + } +} diff --git a/bacula/src/qt-console/tray-monitor/tray-monitor.conf.in b/bacula/src/qt-console/tray-monitor/tray-monitor.conf.in index 30aea7db37..3f72921b2d 100644 --- a/bacula/src/qt-console/tray-monitor/tray-monitor.conf.in +++ b/bacula/src/qt-console/tray-monitor/tray-monitor.conf.in @@ -1,6 +1,9 @@ # # Bacula Tray Monitor Configuration File # +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# Monitor { Name = @basename@-mon diff --git a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.in b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.in index 92247c0ca7..8c414a05ad 100644 --- a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.in +++ b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.in @@ -6,6 +6,9 @@ # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# # CONFIG options for Windows are pulled from win32/qmake.conf CONFIG += qt #CONFIG += qt debug diff --git a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw32.in b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw32.in index 763c03244a..362fff1b30 100644 --- a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw32.in +++ b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw32.in @@ -7,11 +7,16 @@ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # CONFIG options for Windows are pulled from win32/qmake.conf -CONFIG += qt +# +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# + +CONFIG += qt cross-win32 #CONFIG += qt debug cross-win32 { - LIBS += -mwindows -L../../win32/release32 -lbacula + LIBS += -mwindows -L../../win32/release32 -lbacula -lpthread INCLUDEPATH += ../../win32/compat } !cross-win32 { diff --git a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw64.in b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw64.in index 39957e67f9..20719ba890 100644 --- a/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw64.in +++ b/bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw64.in @@ -7,11 +7,16 @@ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # CONFIG options for Windows are pulled from win32/qmake.conf -CONFIG += qt +# +# Copyright (C) 2000-2017 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# + +CONFIG += qt cross-win32 #CONFIG += qt debug cross-win32 { - LIBS += -mwindows -L../../win32/release64 -lbacula + LIBS += -mwindows -L../../win32/release64 -lbacula -lpthread INCLUDEPATH += ../../win32/compat } !cross-win32 {