bool bdb_check_settings(JCR *jcr, int64_t *starttime, int val1, int64_t val2);
bool bdb_open_batch_connexion(JCR *jcr);
bool bdb_check_max_connections(JCR *jcr, uint32_t max_concurrent_jobs);
+ virtual const char *search_op(JCR *jcr, const char *table_col, char *value, POOLMEM **esc, POOLMEM **dest);
/* Acl parts for various SQL commands */
void free_acl(); /* Used internally, free acls tab */
void bdb_list_snapshot_records(JCR *jcr, SNAPSHOT_DBR *sdbr,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
void bdb_list_files(JCR *jcr, FILE_DBR *fr, DB_RESULT_HANDLER *sendit, void *ctx);
-
+ void bdb_list_metadata_records(JCR *jcr, META_DBR *meta_r, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
/* sql_update.c */
bool bdb_update_job_start_record(JCR *jcr, JOB_DBR *jr);
int bdb_update_job_end_record(JCR *jcr, JOB_DBR *jr);
bool sql_batch_start(JCR *jcr);
bool sql_batch_end(JCR *jcr, const char *error);
bool sql_batch_insert(JCR *jcr, ATTR_DBR *ar);
+ const char *search_op(JCR *jcr, const char *table_col, char *value, POOLMEM **esc, POOLMEM **dest);
};
#endif /* __BDB_POSTGRESQL_H_ */
-/*
+/*
Bacula(R) - The Network Backup Solution
- Copyright (C) 2000-2022 Kern Sibbald
+ Copyright (C) 2000-2023 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.
This notice must be preserved when any source code is
conveyed and/or propagated.
- Bacula(R) is a registered trademark of Kern Sibbald.
-*/
-/*
- * Generic catalog class methods.
- *
- * Note: at one point, this file was assembled from parts of other files
- * by a programmer, and other than "wrapping" in a class, which is a trivial
- * change for a C++ programmer, nothing substantial was done, yet all the
- * code was recommitted under this programmer's name. Consequently, we
- * undo those changes here.
- */
-
-#include "bacula.h"
-
-#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
-
-#include "cats.h"
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * Generic catalog class methods.
+ *
+ * Note: at one point, this file was assembled from parts of other files
+ * by a programmer, and other than "wrapping" in a class, which is a trivial
+ * change for a C++ programmer, nothing substantial was done, yet all the
+ * code was recommitted under this programmer's name. Consequently, we
+ * undo those changes here.
+ */
+
+#include "bacula.h"
+
+#if HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL
+
+#include "cats.h"
static int dbglvl=100;
-
+
void append_filter(POOLMEM **buf, char *cond)
{
if (*buf[0] != '\0') {
pm_strcat(buf, cond);
}
-bool BDB::bdb_match_database(const char *db_driver, const char *db_name,
- const char *db_address, int db_port)
+static void append_AND_OR_filter(bool and_or, POOLMEM **buf, char *cond)
{
- BDB *mdb = this;
- bool match;
-
- if (db_driver) {
- match = strcasecmp(mdb->m_db_driver, db_driver) == 0 &&
- bstrcmp(mdb->m_db_name, db_name) &&
- bstrcmp(mdb->m_db_address, db_address) &&
- mdb->m_db_port == db_port &&
- mdb->m_dedicated == false;
+ if (*buf[0] != '\0') {
+ if (and_or) {
+ pm_strcat(buf, " OR ");
+ } else {
+ pm_strcat(buf, " AND ");
+ }
} else {
- match = bstrcmp(mdb->m_db_name, db_name) &&
- bstrcmp(mdb->m_db_address, db_address) &&
- mdb->m_db_port == db_port &&
- mdb->m_dedicated == false;
+ if (and_or) {
+ pm_strcpy(buf, " WHERE ( ");
+ } else {
+ pm_strcat(buf, " WHERE ");
+ }
}
- return match;
+
+ pm_strcat(buf, cond);
}
-BDB *BDB::bdb_clone_database_connection(JCR *jcr, bool mult_db_connections)
-{
- BDB *mdb = this;
- /*
- * See if its a simple clone e.g. with mult_db_connections set to false
- * then we just return the calling class pointer.
- */
- if (!mult_db_connections) {
- mdb->m_ref_count++;
- return mdb;
- }
-
- /*
- * A bit more to do here just open a new session to the database.
- */
- return db_init_database(jcr, mdb->m_db_driver, mdb->m_db_name,
+bool BDB::bdb_match_database(const char *db_driver, const char *db_name,
+ const char *db_address, int db_port)
+{
+ BDB *mdb = this;
+ bool match;
+
+ if (db_driver) {
+ match = strcasecmp(mdb->m_db_driver, db_driver) == 0 &&
+ bstrcmp(mdb->m_db_name, db_name) &&
+ bstrcmp(mdb->m_db_address, db_address) &&
+ mdb->m_db_port == db_port &&
+ mdb->m_dedicated == false;
+ } else {
+ match = bstrcmp(mdb->m_db_name, db_name) &&
+ bstrcmp(mdb->m_db_address, db_address) &&
+ mdb->m_db_port == db_port &&
+ mdb->m_dedicated == false;
+ }
+ return match;
+}
+
+BDB *BDB::bdb_clone_database_connection(JCR *jcr, bool mult_db_connections)
+{
+ BDB *mdb = this;
+ /*
+ * See if its a simple clone e.g. with mult_db_connections set to false
+ * then we just return the calling class pointer.
+ */
+ if (!mult_db_connections) {
+ mdb->m_ref_count++;
+ return mdb;
+ }
+
+ /*
+ * A bit more to do here just open a new session to the database.
+ */
+ return db_init_database(jcr, mdb->m_db_driver, mdb->m_db_name,
mdb->m_db_user, mdb->m_db_password, mdb->m_db_address,
mdb->m_db_port, mdb->m_db_socket,
mdb->m_db_ssl_mode, mdb->m_db_ssl_key,
mdb->m_db_ssl_cert, mdb->m_db_ssl_ca,
mdb->m_db_ssl_capath, mdb->m_db_ssl_cipher,
true, mdb->m_disabled_batch_insert);
-}
-
-const char *BDB::bdb_get_engine_name(void)
-{
- BDB *mdb = this;
- switch (mdb->m_db_driver_type) {
- case SQL_DRIVER_TYPE_MYSQL:
- return "MySQL";
- case SQL_DRIVER_TYPE_POSTGRESQL:
- return "PostgreSQL";
- case SQL_DRIVER_TYPE_SQLITE3:
- return "SQLite3";
- default:
- return "Unknown";
- }
-}
-
-/*
- * Lock database, this can be called multiple times by the same
- * thread without blocking, but must be unlocked the number of
- * times it was locked using db_unlock().
- */
-void BDB::bdb_lock(const char *file, int line)
-{
- int errstat;
- BDB *mdb = this;
-
- if ((errstat = rwl_writelock_p(&mdb->m_lock, file, line)) != 0) {
- berrno be;
- e_msg(file, line, M_FATAL, 0, "rwl_writelock failure. stat=%d: ERR=%s\n",
- errstat, be.bstrerror(errstat));
- }
-}
-
-/*
- * Unlock the database. This can be called multiple times by the
- * same thread up to the number of times that thread called
- * db_lock()/
- */
-void BDB::bdb_unlock(const char *file, int line)
-{
- int errstat;
- BDB *mdb = this;
-
- if ((errstat = rwl_writeunlock(&mdb->m_lock)) != 0) {
- berrno be;
- e_msg(file, line, M_FATAL, 0, "rwl_writeunlock failure. stat=%d: ERR=%s\n",
- errstat, be.bstrerror(errstat));
- }
-}
-
-bool BDB::bdb_sql_query(const char *query, int flags)
-{
- bool retval;
- BDB *mdb = this;
-
- bdb_lock();
- retval = sql_query(query, flags);
- if (!retval) {
- Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
- }
- bdb_unlock();
- return retval;
-}
-
-void BDB::print_lock_info(FILE *fp)
-{
- BDB *mdb = this;
- if (mdb->m_lock.valid == RWLOCK_VALID) {
- fprintf(fp, "\tRWLOCK=%p w_active=%i w_wait=%i\n",
- &mdb->m_lock, mdb->m_lock.w_active, mdb->m_lock.w_wait);
- }
-}
+}
+
+const char *BDB::bdb_get_engine_name(void)
+{
+ BDB *mdb = this;
+ switch (mdb->m_db_driver_type) {
+ case SQL_DRIVER_TYPE_MYSQL:
+ return "MySQL";
+ case SQL_DRIVER_TYPE_POSTGRESQL:
+ return "PostgreSQL";
+ case SQL_DRIVER_TYPE_SQLITE3:
+ return "SQLite3";
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * Lock database, this can be called multiple times by the same
+ * thread without blocking, but must be unlocked the number of
+ * times it was locked using db_unlock().
+ */
+void BDB::bdb_lock(const char *file, int line)
+{
+ int errstat;
+ BDB *mdb = this;
+
+ if ((errstat = rwl_writelock_p(&mdb->m_lock, file, line)) != 0) {
+ berrno be;
+ e_msg(file, line, M_FATAL, 0, "rwl_writelock failure. stat=%d: ERR=%s\n",
+ errstat, be.bstrerror(errstat));
+ }
+}
+
+/*
+ * Unlock the database. This can be called multiple times by the
+ * same thread up to the number of times that thread called
+ * db_lock()/
+ */
+void BDB::bdb_unlock(const char *file, int line)
+{
+ int errstat;
+ BDB *mdb = this;
+
+ if ((errstat = rwl_writeunlock(&mdb->m_lock)) != 0) {
+ berrno be;
+ e_msg(file, line, M_FATAL, 0, "rwl_writeunlock failure. stat=%d: ERR=%s\n",
+ errstat, be.bstrerror(errstat));
+ }
+}
+
+bool BDB::bdb_sql_query(const char *query, int flags)
+{
+ bool retval;
+ BDB *mdb = this;
+
+ bdb_lock();
+ retval = sql_query(query, flags);
+ if (!retval) {
+ Mmsg(mdb->errmsg, _("Query failed: %s: ERR=%s\n"), query, sql_strerror());
+ }
+ bdb_unlock();
+ return retval;
+}
+
+void BDB::print_lock_info(FILE *fp)
+{
+ BDB *mdb = this;
+ if (mdb->m_lock.valid == RWLOCK_VALID) {
+ fprintf(fp, "\tRWLOCK=%p w_active=%i w_wait=%i\n",
+ &mdb->m_lock, mdb->m_lock.w_active, mdb->m_lock.w_wait);
+ }
+}
bool OBJECT_DBR::parse_plugin_object_string(char **obj_str)
{
if (!tmp) {
goto bail_out;
}
- val = str_to_uint64(tmp);
- ObjectCount = (val > 9223372036854775808ULL /*2^63 */) ? 0 : val;
+ ObjectCount = str_to_uint64(*obj_str);
ret = true;
append_filter(where, tmp.c_str());
}
}
-
}
void parse_restore_object_string(char **r_obj_str, ROBJECT_DBR *robj_r)
SAME_KW("EmailImportance", OT_STRING),
SAME_KW("EmailInternetMessageId", OT_STRING),
SAME_KW("EmailIsRead", OT_BOOL),
+ SAME_KW("EmailIsDraft", OT_BOOL),
SAME_KW("EmailTime", OT_STRING),
SAME_KW("EmailSubject", OT_STRING),
SAME_KW("EmailTags", OT_STRING),
return status;
}
+void META_DBR::get_important_keys(POOLMEM **where)
+{
+ if (bstrcasecmp(Type, "email")) {
+ Mmsg(where, "EmailTenant, EmailOwner, EmailFrom, EmailTo, EmailTime, EmailSubject, FileIndex, JobId");
+ } else {
+ Mmsg(where, "AttachmentEmailId, AttachmentSize, AttachmentName, FileIndex, JobId");
+ }
+}
+
+void META_DBR::get_all_keys(POOLMEM **where)
+{
+ struct json_sql *p;
+ if (bstrcasecmp(Type, "email")) {
+ p = email_json_v1;
+ } else {
+ p = email_attachment_json_v1;
+ }
+
+ Mmsg(where, "JobId,FileIndex");
+ for (int i = 0; p[i].sql_name ; i++) {
+ pm_strcat(where, ",");
+ pm_strcat(where, p[i].sql_name);
+ }
+}
+
+void META_DBR::create_db_filter(JCR *jcr, BDB *db, POOLMEM **where)
+{
+ const char *prefix;
+ bool and_or = false;
+ POOL_MEM esc(PM_MESSAGE), tmp(PM_MESSAGE);
+
+ if (bstrcasecmp(Type, "email")) {
+ prefix = "Email";
+ } else {
+ prefix = "Attachment";
+ }
+
+ if (bstrcasecmp(Type, "email"))
+ {
+ if (all && (*From || *To || *Cc || *Subject || *Tags || *BodyPreview || *Category)) {
+ and_or = true;
+ }
+
+ if (Id[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailId", Id, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (From[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailFrom", From, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (To[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailTo", To, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (Cc[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailCc", Cc, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (Subject[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailSubject", Subject, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (Tags[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailTags", Tags, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+
+ if (BodyPreview[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailBodyPreview", BodyPreview, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+#if 0
+ if (Category[0] != 0) {
+ db->search_op(jcr, "MetaEmail.EmailCategory", Category, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+#endif
+
+ if (and_or) {
+ pm_strcat(where, ") ");
+ }
+
+ if (ConversationId[0] != 0) {
+ db_escape_string(jcr, jcr->db, esc.c_str(), ConversationId, strlen(ConversationId));
+ Mmsg(tmp, " MetaEmail.EmailConversationId = '%s'", esc.c_str());
+ append_filter(where, tmp.c_str());
+ }
+
+ if (HasAttachment > 0) {
+ Mmsg(tmp, " MetaEmail.EmailHasAttachment = %d", HasAttachment);
+ append_filter(where, tmp.c_str());
+ }
+
+ if (isDraft > 0) {
+ Mmsg(tmp, " MetaEmail.EmailIsDraft = %d", isDraft);
+ append_filter(where, tmp.c_str());
+ }
+
+ if (isRead > 0) {
+ Mmsg(tmp, " MetaEmail.EmailIsRead = %d", isRead);
+ append_filter(where, tmp.c_str());
+ }
+
+ if (MinTime[0]) {
+ db_escape_string(jcr, jcr->db, esc.c_str(), MinTime, strlen(MinTime));
+ Mmsg(tmp, " MetaEmail.EmailTime >= '%s'", esc.c_str());
+ append_filter(where, tmp.c_str());
+ }
+
+ if (MaxTime[0]) {
+ db_escape_string(jcr, jcr->db, esc.c_str(), MaxTime, strlen(MaxTime));
+ Mmsg(tmp, " MetaEmail.EmailTime <= '%s'", esc.c_str());
+ append_filter(where, tmp.c_str());
+ }
+ } else {
+ if (Id[0] != 0) {
+ db->search_op(jcr, "MetaAttachment.AttachmentEmailId", Id, esc.handle(), tmp.handle());
+ append_AND_OR_filter(and_or, where, tmp.c_str());
+ }
+ }
+
+ if (MinSize > 0) {
+ Mmsg(tmp, " Meta%s.%sSize >= %llu", prefix, prefix, MinSize);
+ append_filter(where, tmp.c_str());
+ }
+
+ if (MaxSize > 0) {
+ Mmsg(tmp, " Meta%s.%sSize <= %llu", prefix, prefix, MaxSize);
+ append_filter(where, tmp.c_str());
+ }
+
+ if (Plugin[0] != 0) {
+ db_escape_string(jcr, jcr->db, esc.c_str(), Plugin, strlen(Plugin));
+ Mmsg(tmp, " Meta%s.Plugin='%s'", prefix, esc.c_str());
+ append_filter(where, tmp.c_str());
+ }
+
+ if (JobId != 0) {
+ Mmsg(tmp, " Meta%s.JobId=%lu", prefix, JobId);
+ append_filter(where, tmp.c_str());
+ }
+}
#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
utime_t JobRetention;
char Name[MAX_NAME_LENGTH]; /* Client name */
char Uname[MAX_UNAME_LENGTH]; /* Uname for client */
- char Plugin[MAX_PLUGIN_LENGTH]; /* Plugin list for this client */
+ char Plugins[MAX_PLUGIN_LENGTH]; /* Plugin list for this client */
};
/* Counter record as in database */
uint64_t *aclbits_extra); /* Extra ACL used */
};
+
+
+/* Used to search in MetaEmail and MetaAttachment table */
+#define MAX_SEARCH_LENGTH 256
+#define MAX_SEARCH_LENGTH_ESCAPED (MAX_SEARCH_LENGTH * 2 + 1)
+
+class META_DBR: public SMARTALLOC
+{
+public:
+ JobId_t JobId;
+ int64_t MinSize;
+ int64_t MaxSize;
+ int HasAttachment;
+ int isDraft;
+ int isRead;
+ uint64_t offset;
+ uint32_t limit;
+ int order;
+ int orderby; // 0: JobId, FileIndex 1: EmailTime
+ bool all;
+
+ char Id[MAX_SEARCH_LENGTH];
+ char Tenant[MAX_SEARCH_LENGTH];
+ char Owner[MAX_SEARCH_LENGTH];
+ char ClientName[MAX_NAME_LENGTH];
+ char From[MAX_SEARCH_LENGTH];
+ char To[MAX_SEARCH_LENGTH];
+ char Cc[MAX_SEARCH_LENGTH];
+ char Tags[MAX_SEARCH_LENGTH];
+ char Subject[MAX_SEARCH_LENGTH];
+ char BodyPreview[MAX_SEARCH_LENGTH];
+ char Type[16]; // Email or Attachment
+ char ConversationId[MAX_NAME_LENGTH];
+ char Category[MAX_SEARCH_LENGTH];
+ char MinTime[MAX_NAME_LENGTH];
+ char MaxTime[MAX_NAME_LENGTH];
+ char Plugin[MAX_NAME_LENGTH];
+ META_DBR(): JobId(0), MinSize(-1), MaxSize(-1), HasAttachment(-1),
+ isDraft(-1), isRead(-1), offset(0), limit(512), order(0), orderby(0), all(false)
+ {
+ *Id = *Tenant = *Owner = 0;
+ *ClientName = *From = *To = *Cc = *Subject = *Tags = 0;
+ *BodyPreview = *Type = *ConversationId = *Category = 0;
+ *MinTime = *MaxTime = *Plugin = 0;
+ };
+ ~META_DBR() {};
+ void get_important_keys(POOLMEM **dest);
+ void get_all_keys(POOLMEM **dest);
+ void create_db_filter(JCR *jcr, BDB *db, POOLMEM **dest);
+};
+
/* Call back context for getting a 32/64 bit value from the database */
class db_int64_ctx {
public:
return true;
}
+const char *BDB_POSTGRESQL::search_op(JCR *jcr, const char *table_col, char *value, POOLMEM **esc, POOLMEM **dest)
+{
+ int len = strlen(value);
+ *esc = check_pool_memory_size(*esc, len*2+1);
+ bdb_escape_string(jcr, *esc, value, len);
+ Mmsg(dest, " %s %%> '%s'", table_col, *esc);
+ return *dest;
+}
#endif /* HAVE_POSTGRESQL */
mdb->bdb_list_files(jcr, filedbr, sendit, ua);
#define db_list_tag_records(jcr, mdb, tagdbr, sendit, ua, llist) \
mdb->bdb_list_tag_records(jcr, tagdbr, sendit, ua, llist);
+#define db_list_metadata_records(jcr, mdb, dbr, sendit, ua, llist) \
+ mdb->bdb_list_metadata_records(jcr, dbr, sendit, ua, llist);
/* sql_update.c */
#define db_update_job_start_record(jcr, mdb, jr) \
*aclbits_extra_ = aclbits_extra;
}
+const char *BDB::search_op(JCR *jcr, const char *table_col, char *value, POOLMEM **esc, POOLMEM **dest)
+{
+ int len = strlen(value);
+ *esc = check_pool_memory_size(*esc, len*2+1);
+ bdb_escape_string(jcr, *esc, (char*)value, len);
+ Mmsg(dest, " %s ILIKE '%%%s%%'", table_col, value);
+ return *dest;
+}
+
#ifdef COMMUNITY
bool BDB::bdb_check_settings(JCR *jcr, int64_t *starttime, int val, int64_t val2)
{
/*
Bacula(R) - The Network Backup Solution
- Copyright (C) 2000-2022 Kern Sibbald
+ Copyright (C) 2000-2023 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.
/*
* Submit general SQL query
*/
-int BDB::bdb_list_sql_query(JCR *jcr, const char *title, const char *query, DB_LIST_HANDLER *sendit,
+int BDB::bdb_list_sql_query(JCR *jcr, const char *query, DB_LIST_HANDLER *sendit,
void *ctx, int verbose, e_list_type type)
{
bdb_lock();
return 0;
}
- list_result(jcr,this, title, sendit, ctx, type);
+ list_result(jcr,this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return 1;
return;
}
- list_result(jcr, this, "pool", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
{
bdb_lock();
if (type == VERT_LIST || type == JSON_LIST) {
- Mmsg(cmd, "SELECT ClientId,Name,Uname,Plugin,AutoPrune,FileRetention,"
+ Mmsg(cmd, "SELECT ClientId,Name,Uname,Plugins,AutoPrune,FileRetention,"
"JobRetention "
- "FROM Client %s ORDER BY ClientId", get_acls(DB_ACL_BIT(DB_ACL_RBCLIENT), true));
+ "FROM Client %s ORDER BY ClientId", get_acl(DB_ACL_CLIENT, true));
} else {
Mmsg(cmd, "SELECT ClientId,Name,FileRetention,JobRetention "
- "FROM Client %s ORDER BY ClientId", get_acls(DB_ACL_BIT(DB_ACL_RBCLIENT), true));
+ "FROM Client %s ORDER BY ClientId", get_acl(DB_ACL_CLIENT, true));
}
if (!QueryDB(jcr, cmd)) {
bdb_unlock();
return;
}
- list_result(jcr, this, "client", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "objecttype", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "object", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "object", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "restoreobject", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "media", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
{
POOL_MEM where2;
bdb_lock();
- const char *where_and = "WHERE";
-
/* Get some extra SQL parameters if needed */
const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) |
DB_ACL_BIT(DB_ACL_FILESET) |
- DB_ACL_BIT(DB_ACL_BCLIENT), true);
+ DB_ACL_BIT(DB_ACL_CLIENT), (JobId == 0 || volume != NULL));
const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB) |
DB_ACL_BIT(DB_ACL_FILESET) |
- DB_ACL_BIT(DB_ACL_BCLIENT)) : "";
+ DB_ACL_BIT(DB_ACL_CLIENT)) : "";
- if (*where) {
- where_and = "AND";
- }
if (JobId) {
- Mmsg(where2, " %s JobMedia.JobId=%lu ", where_and, JobId);
- where_and = "AND";
+ Mmsg(where2, " WHERE JobMedia.JobId=%lu ", JobId);
}
+
if (volume) {
POOL_MEM tmp, tmp2;
int len = strlen(volume);
tmp.check_size(len*2+1);
db_escape_string(jcr, this, tmp.c_str(), volume, len);
- Mmsg(tmp2, " %s Media.VolumeName = '%s' ", where_and, tmp.c_str());
+ Mmsg(tmp2, " %s Media.VolumeName = '%s' ", JobId == 0 ?"WHERE": "AND", tmp.c_str());
pm_strcat(where2, tmp2.c_str());
}
"FROM JobMedia JOIN Media USING (MediaId) %s "
"%s %s ORDER BY JobMediaId ASC",
join,
- where,
- where2.c_str());
+ where2.c_str(),
+ where);
} else {
Mmsg(cmd, "SELECT JobId,Media.VolumeName,FirstIndex,LastIndex "
"FROM JobMedia JOIN Media USING (MediaId) %s %s %s ORDER BY JobMediaId ASC",
join,
- where,
- where2.c_str());
+ where2.c_str(),
+ where);
}
Dmsg1(DT_SQL|50, "q=%s\n", cmd);
return;
}
- list_result(jcr, this, "jobmedia", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return;
}
- list_result(jcr, this, "filemedia", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
}
+
void BDB::bdb_list_copies_records(JCR *jcr, uint32_t limit, char *JobIds,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
}
bdb_lock();
- const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_BCLIENT), false);
- const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_BCLIENT)) : "";
+ const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_CLIENT), false);
+ const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)) : "";
Mmsg(cmd,
"SELECT DISTINCT Job.PriorJobId AS JobId, Job.Job, "
sendit(ctx, _("The catalog contains copies as follows:\n"));
}
- list_result(jcr, this, "copy", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
}
sql_free_result();
if (!QueryDB(jcr, cmd)) {
goto bail_out;
}
- list_result(jcr, this, "event", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
bail_out:
bdb_unlock();
const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) |
DB_ACL_BIT(DB_ACL_FILESET) |
- DB_ACL_BIT(DB_ACL_RBCLIENT)
- , false);
+ DB_ACL_BIT(DB_ACL_CLIENT), false);
const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB) |
DB_ACL_BIT(DB_ACL_FILESET) |
- DB_ACL_BIT(DB_ACL_RBCLIENT)) : "";
-
+ DB_ACL_BIT(DB_ACL_CLIENT)) : "";
+
if (type == VERT_LIST || type == JSON_LIST) {
Mmsg(cmd, "SELECT Time,LogText FROM Log %s "
"WHERE Log.JobId=%s %s ORDER BY LogId ASC",
goto bail_out;
}
- list_result(jcr, this, "joblog", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
pm_strcat(where, where_tmp);
if (*where_tmp) {
- join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_RBCLIENT) |
+ join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT) |
DB_ACL_BIT(DB_ACL_FILESET));
}
"Job.ClientId,Client.Name as ClientName,JobStatus,SchedTime,"
"StartTime,EndTime,RealEndTime,JobTDate,"
"VolSessionId,VolSessionTime,JobFiles,JobBytes,ReadBytes,JobErrors,"
- "JobMissingFiles,Job.PoolId,Pool.Name as PoolName,PriorJobId,PriorJob,"
+ "JobMissingFiles,Job.PoolId,Pool.Name as PoolName,PriorJobId,"
"Job.FileSetId,FileSet.FileSet,Job.HasCache,Comment,Reviewed "
"FROM Job JOIN Client USING (ClientId) LEFT JOIN Pool USING (PoolId) "
"LEFT JOIN FileSet USING (FileSetId) %s "
"FROM Job %s %s ORDER BY StartTime %s,JobId %s %s",
join, where, order, order, limit);
break;
- case LAST_JOBS:
- Mmsg(cmd,
- "SELECT JobId,Client1.Name as Client,Job.Name as Name,StartTime,Level as "
- "JobLevel,JobFiles,JobBytes "
- "FROM Client AS Client1 JOIN Job USING (ClientId) %s %s AND JobStatus IN ('T','W') "
- "ORDER BY StartTime %s %s", join, where, order, limit);
-
default:
break;
}
}
}
sql_data_seek(0);
- list_result(jcr, this, "job", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
sql_free_result();
bdb_unlock();
return list;
/*
* List Job totals
*
- * TODO: Adapt for JSON
*/
void BDB::bdb_list_job_totals(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx)
{
bdb_lock();
- const char *where = get_acls(DB_ACL_BIT(DB_ACL_BCLIENT) | DB_ACL_BIT(DB_ACL_JOB), true);
- const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_BCLIENT)) : "";
+ const char *where = get_acls(DB_ACL_BIT(DB_ACL_CLIENT) | DB_ACL_BIT(DB_ACL_JOB), true);
+ const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)) : "";
/* List by Job */
Mmsg(cmd, "SELECT count(*) AS Jobs,sum(JobFiles) "
return;
}
- list_result(jcr, this, "jobtotal", sendit, ctx, HORZ_LIST);
+ list_result(jcr, this, sendit, ctx, HORZ_LIST);
sql_free_result();
return;
}
- list_result(jcr, this, "jobtotal", sendit, ctx, HORZ_LIST);
+ list_result(jcr, this, sendit, ctx, HORZ_LIST);
sql_free_result();
bdb_unlock();
bdb_lock();
/* Get optional filters for the SQL query */
const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) |
- DB_ACL_BIT(DB_ACL_BCLIENT) |
+ DB_ACL_BIT(DB_ACL_CLIENT) |
DB_ACL_BIT(DB_ACL_FILESET), true);
const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB) |
- DB_ACL_BIT(DB_ACL_BCLIENT) |
+ DB_ACL_BIT(DB_ACL_CLIENT) |
DB_ACL_BIT(DB_ACL_FILESET)) : "";
/*
char ed1[50];
bdb_lock();
- const char *where = get_acl(DB_ACL_BCLIENT, false);
+ const char *where = get_acl(DB_ACL_CLIENT, false);
*filter = 0;
if (sdbr->Name[0]) {
goto bail_out;
}
- list_result(jcr, this, "snapshot", sendit, ctx, type);
+ list_result(jcr, this, sendit, ctx, type);
bail_out:
sql_free_result();
}
}
Dmsg1(DT_SQL|50, "q=%s\n", tmp.c_str());
- bdb_list_sql_query(jcr, "tag", tmp.c_str(), result_handler, ctx, 0, type);
+ bdb_list_sql_query(jcr, tmp.c_str(), result_handler, ctx, 0, type);
}
bdb_unlock();
}
-/* List all file records from a job
- * "deleted" values are described just below
+/*
+ * List plugin objects (search is based on object provided)
*/
-void BDB::bdb_list_jobs_for_file(JCR *jcr, const char *client, const char *fname, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
+void BDB::bdb_list_metadata_records(JCR *jcr, META_DBR *meta_r, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
- if (!client || !*client || !fname || !*fname) {
- return;
+ POOL_MEM esc(PM_MESSAGE), tmp(PM_MESSAGE), where(PM_MESSAGE), join(PM_MESSAGE);
+
+ bdb_lock();
+
+ //TODO add ACL part
+ meta_r->create_db_filter(jcr, this, where.handle());
+ Dmsg1(DT_SQL|50, "where=[%s]\n", where.c_str());
+
+ if (meta_r->ClientName[0] != 0) {
+ bdb_escape_string(jcr, esc.c_str(), meta_r->ClientName, strlen(meta_r->ClientName));
+ Mmsg(tmp, " Client.Name='%s'", esc.c_str());
+ append_filter(where.handle(), tmp.c_str());
+ Mmsg(join, " JOIN Job USING (JobId) JOIN Client USING (ClientId) ");
}
- const char *concat="Path.Path||File.Filename";
- if (bdb_get_type_index() == SQL_TYPE_MYSQL) {
- concat = " CONCAT(Path.Path,File.Filename) ";
+ if (meta_r->orderby == 1) {
+ Mmsg(tmp, " ORDER BY EmailTime %s ", meta_r->order ? "DESC" : "ASC");
+ } else {
+ Mmsg(tmp, " ORDER BY JobId, FileIndex %s ", meta_r->order ? "DESC" : "ASC");
}
- bdb_lock();
- /* Get optional filters for the SQL query */
- const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) |
- DB_ACL_BIT(DB_ACL_BCLIENT) |
- DB_ACL_BIT(DB_ACL_FILESET), false);
+ pm_strcat(where, tmp.c_str());
- const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_FILESET)) : "";
-
- int len = strlen(fname);
- char *esc = (char *)malloc(len * 2 + 1);
- bdb_escape_string(jcr, esc, (char *)fname, len);
-
- len = strlen(client);
- char *esc2 = (char *)malloc(len * 2 + 1);
- bdb_escape_string(jcr, esc2, (char *)client, len);
-
- Mmsg(cmd, "SELECT Job.JobId as JobId,"
- "%s as Name, " // Concat of the filename
- "StartTime, Type as JobType, JobStatus,JobFiles,JobBytes "
- "FROM Client JOIN Job USING (ClientId) JOIN File USING (JobId) JOIN Path USING (PathId) %s "
- "WHERE Client.Name = '%s' "
- "AND File.FileIndex > 0 "
- "AND File.Filename='%s' %s ORDER BY StartTime DESC LIMIT 20", concat, join, esc2, esc, where);
-
- free(esc);
- free(esc2);
- Dmsg1(DT_SQL|50, "q=%s\n", cmd);
+ if (meta_r->limit > 0) {
+ Mmsg(tmp, " LIMIT %d ", meta_r->limit);
+ pm_strcat(where, tmp.c_str());
+ }
+
+ if (meta_r->offset > 0) {
+ Mmsg(tmp, " OFFSET %ld ", meta_r->offset);
+ pm_strcat(where, tmp.c_str());
+ }
+
+ switch (type) {
+ case JSON_LIST:
+ case VERT_LIST:
+ meta_r->get_all_keys(tmp.handle());
+ Mmsg(cmd,
+ "SELECT %s "
+ "FROM Meta%s %s %s", tmp.c_str(), meta_r->Type, join.c_str(), where.c_str());
+ break;
+ case HORZ_LIST:
+ meta_r->get_important_keys(tmp.handle());
+ Mmsg(cmd,
+ "SELECT %s "
+ "FROM Meta%s %s %s", tmp.c_str(), meta_r->Type, join.c_str(), where.c_str());
+ break;
+ default:
+ break;
+ }
+ Dmsg1(DT_SQL|50, "%s\n", cmd);
if (!QueryDB(jcr, cmd)) {
- goto bail_out;
+ Jmsg(jcr, M_ERROR, 0, _("Query %s failed!\n"), cmd);
+ bdb_unlock();
+ return;
}
- list_result(jcr, this, "job", sendit, ctx, HORZ_LIST);
-bail_out:
+ list_result(jcr, this, sendit, ctx, type);
+
sql_free_result();
bdb_unlock();
+
}
+
#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
bdb_escape_string(jcr, esc_name, cr->Name, strlen(cr->Name));
bdb_escape_string(jcr, esc_uname, cr->Uname, strlen(cr->Uname));
- bdb_escape_string(jcr, esc_plugin, cr->Plugin, strlen(cr->Plugin));
+ bdb_escape_string(jcr, esc_plugin, cr->Plugins, strlen(cr->Plugins));
Mmsg(cmd,
"UPDATE Client SET AutoPrune=%d,FileRetention=%s,JobRetention=%s,"
-"Uname='%s',Plugin='%s' WHERE Name='%s'",
+"Uname='%s',Plugins='%s' WHERE Name='%s'",
cr->AutoPrune,
edit_uint64(cr->FileRetention, ed1),
edit_uint64(cr->JobRetention, ed2),
meta_pkt mp(p);
META_JSON parser;
POOL_MEM val;
- Dmsg3(50, "[metadata plugin] type=%d len=%d buf=[%.*s]\n", mp.type, mp.buf_len, mp.buf);
+ Dmsg4(150, "[metadata plugin] type=%d len=%d buf=[%.*s]\n", mp.type, mp.buf_len, mp.buf_len, mp.buf);
if (parser.parse(jcr,
jcr->db,
jcr->wjcr? jcr->wjcr->JobId : jcr->JobId,
}
db_unlock(jcr->db);
} else {
- Jmsg1(jcr, M_ERROR, 0, _("Unable to parse Plugin metadata for FileIndex %lld\n"), (int64_t)FileIndex);
+ Jmsg(jcr, M_ERROR, 0, _("Unable to parse Plugin metadata for FileIndex %lld\n"), (int64_t)FileIndex);
Dmsg1(50, "Unable to parse Plugin metadata err=%s\n", val.c_str());
}
char *pos = strchr(fd->msg+strlen(OKjob)+1, ';');
if (pos) {
*pos = 0;
- bstrncpy(cr.Plugin, pos+1, sizeof(cr.Plugin));
+ bstrncpy(cr.Plugins, pos+1, sizeof(cr.Plugins));
}
bstrncpy(cr.Uname, fd->msg+strlen(OKjob)+1, sizeof(cr.Uname));
* list objects [type=objecttype job_id=id clientname=n,status=S] - list plugin objects
* list pluginrestoreconf jobid=x,y,z [id=k]
* list filemedia jobid=x fileindex=z
- * list metadata type=email [search=<str> where=<from|to|cc|tags|subject|bodypreview|attachement> importance=<str> isread=<yes|no> isdraft=<yes|no> hasattachement=<yes|no> limit=<int> offset=<int> receivedtime=<time> senttime=<time> order=<asc|desc>]
+ * list metadata type=[email|attachment] from=<str> to=<str> cc=<str> tags=<str>
+ * subject=<str> bodypreview=<str> all=<str> minsize=<int> maxsize=<int>
+ * importance=<str> isread=<0|1> isdraft=<0|1>
+ * categories=<str> conversationid=<str> hasattachment=<0|1>
+ * starttime=<time> endtime=<time>
+ * limit=<int> offset=<int> order=<Asc|desc>
+ * emailid=<str>
*
- * list metadata type=email where="(EmailFrom ILIKE '%test%' OR EmailTo ILIKE '%test%' OR EmailCc ILIKE '%test%') AND EmailReceivedtime > '2021-09-01 00:00:00'" limit=10
- *
* Note: keyword "long" is before the first command on the command
* line results in doing a llist (long listing).
*/
} else if (strcasecmp(ua->argk[j], NT_("order")) == 0 && ua->argv[j]) {
/* Other order are tested before */
- event.order = bstrcasecmp(ua->argv[j], "DESC") == 0;
+ event.order = bstrcasecmp(ua->argv[j], "DESC");
} else if (strcasecmp(ua->argk[j], NT_("days")) == 0 && ua->argv[j]) {
if (!setup_start_date(ua->argv[j], true, event.start, sizeof(event.start))) {
} else if (strcasecmp(ua->argk[i], NT_("tag")) == 0) {
return tag_cmd(ua, cmd);
+ /* List Emails/Attachments */
+ } else if (strcasecmp(ua->argk[i], NT_("metadata")) == 0) {
+ META_DBR meta_r;
+
+ for (j=i+1; j<ua->argc; j++) {
+ if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
+ if (is_a_number(ua->argv[j]) && acl_access_jobid_ok(ua, ua->argv[j])) {
+ meta_r.JobId = str_to_uint64(ua->argv[j]);
+ } else {
+ ua->error_msg(_("Invalid jobid argument\n"));
+ return 1;
+ }
+
+ } else if (strcasecmp(ua->argk[j], NT_("client")) == 0) {
+ if (!acl_access_ok(ua, Client_ACL, ua->argk[j])) {
+ ua->error_msg(_("Access to Client=%s not authorized.\n"), ua->argk[j]);
+ return 0;
+ }
+ bstrncpy(meta_r.ClientName, ua->argv[j], sizeof(meta_r.ClientName));
+
+ } else if (strcasecmp(ua->argk[j], NT_("from")) == 0) {
+ bstrncpy(meta_r.From, ua->argv[j], sizeof(meta_r.From));
+
+ } else if (strcasecmp(ua->argk[j], NT_("emailid")) == 0) {
+ bstrncpy(meta_r.Id, ua->argv[j], sizeof(meta_r.Id));
+
+ } else if (strcasecmp(ua->argk[j], NT_("to")) == 0) {
+ bstrncpy(meta_r.To, ua->argv[j], sizeof(meta_r.To));
+
+ } else if (strcasecmp(ua->argk[j], NT_("cc")) == 0) {
+ bstrncpy(meta_r.Cc, ua->argv[j], sizeof(meta_r.Cc));
+
+ } else if (strcasecmp(ua->argk[j], NT_("tags")) == 0) {
+ bstrncpy(meta_r.Tags, ua->argv[j], sizeof(meta_r.Tags));
+
+ } else if (strcasecmp(ua->argk[j], NT_("subject")) == 0) {
+ bstrncpy(meta_r.Subject, ua->argv[j], sizeof(meta_r.Subject));
+
+ } else if (strcasecmp(ua->argk[j], NT_("bodypreview")) == 0) {
+ bstrncpy(meta_r.BodyPreview, ua->argv[j], sizeof(meta_r.BodyPreview));
+
+ } else if (strcasecmp(ua->argk[j], NT_("type")) == 0) {
+ bstrncpy(meta_r.Type, ua->argv[j], sizeof(meta_r.Type));
+
+ } else if (strcasecmp(ua->argk[j], NT_("conversationid")) == 0) {
+ bstrncpy(meta_r.ConversationId, ua->argv[j], sizeof(meta_r.ConversationId));
+
+ } else if (strcasecmp(ua->argk[j], NT_("category")) == 0) {
+ bstrncpy(meta_r.Category, ua->argv[j], sizeof(meta_r.Category));
+
+ } else if (strcasecmp(ua->argk[j], NT_("all")) == 0) {
+ bstrncpy(meta_r.Tags, ua->argv[j], sizeof(meta_r.Tags));
+ bstrncpy(meta_r.From, ua->argv[j], sizeof(meta_r.From));
+ bstrncpy(meta_r.To, ua->argv[j], sizeof(meta_r.To));
+ bstrncpy(meta_r.Cc, ua->argv[j], sizeof(meta_r.Cc));
+ bstrncpy(meta_r.Subject, ua->argv[j], sizeof(meta_r.Subject));
+ bstrncpy(meta_r.BodyPreview, ua->argv[j], sizeof(meta_r.BodyPreview));
+ bstrncpy(meta_r.Category, ua->argv[j], sizeof(meta_r.Category));
+ meta_r.all = true;
+
+ } else if (strcasecmp(ua->argk[j], NT_("minsize")) == 0) {
+ uint64_t v;
+ if (size_to_uint64(ua->argv[j], strlen(ua->argv[j]), &v)) {
+ meta_r.MinSize = v;
+ }
+
+ } else if (strcasecmp(ua->argk[j], NT_("maxsize")) == 0) {
+ uint64_t v;
+ if (size_to_uint64(ua->argv[j], strlen(ua->argv[j]), &v)) {
+ meta_r.MaxSize = v;
+ }
+
+ } else if (strcasecmp(ua->argk[j], NT_("mintime")) == 0) {
+ bstrncpy(meta_r.MinTime, ua->argv[j], sizeof(meta_r.MinTime));
+
+ } else if (strcasecmp(ua->argk[j], NT_("maxtime")) == 0) {
+ bstrncpy(meta_r.MaxTime, ua->argv[j], sizeof(meta_r.MaxTime));
+
+ } else if (strcasecmp(ua->argk[j], NT_("isread")) == 0) {
+ meta_r.isRead = str_to_uint64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("isdraft")) == 0) {
+ meta_r.isDraft = str_to_uint64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("hasattachment")) == 0) {
+ meta_r.HasAttachment = str_to_uint64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("offset")) == 0 && ua->argv[j]) {
+ meta_r.offset = atoi(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
+ meta_r.limit = atoi(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("orderby")) == 0 && ua->argv[j]) {
+ meta_r.orderby = bstrcasecmp(ua->argv[j], "Time") == 1;
+
+ } else if (strcasecmp(ua->argk[j], NT_("order")) == 0 && ua->argv[j]) {
+ /* Other order are tested before */
+ meta_r.order = bstrcasecmp(ua->argv[j], "DESC");
+ }
+ }
+ if (*meta_r.Type == 0) {
+ ua->error_msg(_("Invalid type argument\n"));
+ return 1;
+ }
+ db_list_metadata_records(ua->jcr, ua->db, &meta_r, prtit, ua, llist);
+ return 1;
+
} else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
|| strcasecmp(ua->argk[i], NT_("days")) == 0
|| strcasecmp(ua->argk[i], NT_("hours")) == 0