/* * Update Client record * Returns: false on failure * true on success */ bool db_update_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cr) { bool retval = false; char ed1[50], ed2[50]; char esc_name[MAX_ESCAPE_NAME_LENGTH]; char esc_uname[MAX_ESCAPE_NAME_LENGTH]; CLIENT_DBR tcr; db_lock(mdb); memcpy(&tcr, cr, sizeof(tcr)); if (!db_create_client_record(jcr, mdb, &tcr)) { goto bail_out; } mdb->db_escape_string(jcr, esc_name, cr->Name, strlen(cr->Name)); mdb->db_escape_string(jcr, esc_uname, cr->Uname, strlen(cr->Uname)); Mmsg(mdb->cmd, "UPDATE Client SET AutoPrune=%d,FileRetention=%s,JobRetention=%s," "Uname='%s' WHERE Name='%s'", cr->AutoPrune, edit_uint64(cr->FileRetention, ed1), edit_uint64(cr->JobRetention, ed2), esc_uname, esc_name); retval = UPDATE_DB(jcr, mdb, mdb->cmd); bail_out: db_unlock(mdb); return retval; }
/* * Get or create a Client record for this Job */ bool get_or_create_client_record(JCR *jcr) { CLIENT_DBR cr; memset(&cr, 0, sizeof(cr)); bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name)); cr.AutoPrune = jcr->client->AutoPrune; cr.FileRetention = jcr->client->FileRetention; cr.JobRetention = jcr->client->JobRetention; if (!jcr->client_name) { jcr->client_name = get_pool_memory(PM_NAME); } pm_strcpy(jcr->client_name, jcr->client->hdr.name); if (!db_create_client_record(jcr, jcr->db, &cr)) { Jmsg(jcr, M_FATAL, 0, _("Could not create Client record. ERR=%s\n"), db_strerror(jcr->db)); return false; } jcr->jr.ClientId = cr.ClientId; if (cr.Uname[0]) { if (!jcr->client_uname) { jcr->client_uname = get_pool_memory(PM_NAME); } pm_strcpy(jcr->client_uname, cr.Uname); } Dmsg2(100, "Created Client %s record %d\n", jcr->client->hdr.name, jcr->jr.ClientId); return true; }
/* * Get or create a Client record for this Job */ bool get_or_create_client_record(JCR *jcr) { CLIENT_DBR cr; memset(&cr, 0, sizeof(cr)); bstrncpy(cr.Name, jcr->res.client->hdr.name, sizeof(cr.Name)); cr.AutoPrune = jcr->res.client->AutoPrune; cr.FileRetention = jcr->res.client->FileRetention; cr.JobRetention = jcr->res.client->JobRetention; if (!jcr->client_name) { jcr->client_name = get_pool_memory(PM_NAME); } pm_strcpy(jcr->client_name, jcr->res.client->hdr.name); if (!db_create_client_record(jcr, jcr->db, &cr)) { Jmsg(jcr, M_FATAL, 0, _("Could not create Client record. ERR=%s\n"), db_strerror(jcr->db)); return false; } /* * Only initialize quota when a Soft or Hard Limit is set. */ if (jcr->res.client->HardQuota != 0 || jcr->res.client->SoftQuota != 0) { if (!db_get_quota_record(jcr, jcr->db, &cr)) { if (!db_create_quota_record(jcr, jcr->db, &cr)) { Jmsg(jcr, M_FATAL, 0, _("Could not create Quota record. ERR=%s\n"), db_strerror(jcr->db)); } jcr->res.client->QuotaLimit = 0; jcr->res.client->GraceTime = 0; } } jcr->jr.ClientId = cr.ClientId; jcr->res.client->QuotaLimit = cr.QuotaLimit; jcr->res.client->GraceTime = cr.GraceTime; if (cr.Uname[0]) { if (!jcr->client_uname) { jcr->client_uname = get_pool_memory(PM_NAME); } pm_strcpy(jcr->client_uname, cr.Uname); } Dmsg2(100, "Created Client %s record %d\n", jcr->res.client->hdr.name, jcr->jr.ClientId); return true; }
static int purge_quota_from_client(UAContext *ua, CLIENTRES *client) { CLIENT_DBR cr; memset(&cr, 0, sizeof(cr)); bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { return 0; } if (!db_create_quota_record(ua->jcr, ua->db, &cr)) { return 0; } if (!db_reset_quota_record(ua->jcr, ua->db, &cr)) { return 0; } ua->info_msg(_("Purged quota for Client \"%s\"\n"), cr.Name); return 1; }
/* * Purge Job records from the database. For any Job which * is older than the retention period, we unconditionally delete * it and all File records for that Job. This is simple enough that no * temporary tables are needed. We simply make an in memory list of * the JobIds then delete the Job, Files, and JobMedia records in that list. */ static int purge_jobs_from_client(UAContext *ua, CLIENTRES *client) { struct del_ctx del; POOL_MEM query(PM_MESSAGE); CLIENT_DBR cr; char ed1[50]; memset(&cr, 0, sizeof(cr)); bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { return 0; } memset(&del, 0, sizeof(del)); del.max_ids = 1000; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); del.PurgedFiles = (char *)malloc(del.max_ids); ua->info_msg(_("Begin purging jobs from Client \"%s\"\n"), cr.Name); Mmsg(query, select_jobs_from_client, edit_int64(cr.ClientId, ed1)); Dmsg1(150, "select sql=%s\n", query.c_str()); db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del); purge_job_list_from_catalog(ua, del); if (del.num_ids == 0) { ua->warning_msg(_("No Files found for client %s to purge from %s catalog.\n"), client->name(), client->catalog->name()); } else { ua->info_msg(_("%d Jobs for client %s purged from %s catalog.\n"), del.num_ids, client->name(), client->catalog->name()); } if (del.JobId) { free(del.JobId); } if (del.PurgedFiles) { free(del.PurgedFiles); } return 1; }
/* * Prune Directory meta data records from the database. */ static bool prune_directory(UAContext *ua, CLIENTRES *client) { int i, len; CLIENT_DBR cr; char *prune_topdir = NULL; POOL_MEM query(PM_MESSAGE), temp(PM_MESSAGE); bool recursive = false; bool retval = false; /* * See if a client was selected. */ if (!client) { if (!get_yesno(ua, _("No client restriction given really remove " "directory for all clients (yes/no): ")) || ua->pint32_val == 0) { if (!(client = get_client_resource(ua))) { return false; } } } /* * See if we need to recursively remove all directories under a certain path. */ recursive = find_arg(ua, NT_("recursive")) >= 0; /* * Get the directory to prune. */ i = find_arg_with_value(ua, NT_("directory")); if (i >= 0) { pm_strcpy(temp, ua->argv[i]); } else { if (recursive) { if (!get_cmd(ua, _("Please enter the full path prefix to remove: "), false)) { return false; } } else { if (!get_cmd(ua, _("Please enter the full path to remove: "), false)) { return false; } } pm_strcpy(temp, ua->cmd); } /* * See if the directory ends in a / and escape it for usage in a database query. */ len = strlen(temp.c_str()); if (*(temp.c_str() + len - 1) != '/') { pm_strcat(temp, "/"); len++; } prune_topdir = (char *)malloc(len * 2 + 1); db_escape_string(ua->jcr, ua->db, prune_topdir, temp.c_str(), len); /* * Remove all files in particular directory. */ if (recursive) { Mmsg(query, "DELETE FROM file WHERE pathid IN (" "SELECT pathid FROM path " "WHERE path LIKE '%s%%'" ")", prune_topdir); } else { Mmsg(query, "DELETE FROM file WHERE pathid IN (" "SELECT pathid FROM path " "WHERE path LIKE '%s'" ")", prune_topdir); } if (client) { char ed1[50]; memset(&cr, 0, sizeof(cr)); bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { goto bail_out; } Mmsg(temp, " AND JobId IN (" "SELECT JobId FROM Job " "WHERE ClientId=%s" ")", edit_int64(cr.ClientId, ed1)); pm_strcat(query, temp.c_str()); } db_lock(ua->db); db_sql_query(ua->db, query.c_str()); db_unlock(ua->db); /* * If we removed the entries from the file table without limiting it to a * certain client we created orphaned path entries as no one is referencing * them anymore. */ if (!client) { if (!get_yesno(ua, _("Cleanup orphaned path records (yes/no):")) || ua->pint32_val == 0) { retval = true; goto bail_out; } if (recursive) { Mmsg(query, "DELETE FROM path " "WHERE path LIKE '%s%%'", prune_topdir); } else { Mmsg(query, "DELETE FROM path " "WHERE path LIKE '%s'", prune_topdir); } db_lock(ua->db); db_sql_query(ua->db, query.c_str()); db_unlock(ua->db); } retval = true; bail_out: if (prune_topdir) { free(prune_topdir); } return retval; }
/* * Pruning Jobs is a bit more complicated than purging Files * because we delete Job records only if there is a more current * backup of the FileSet. Otherwise, we keep the Job record. * In other words, we never delete the only Job record that * contains a current backup of a FileSet. This prevents the * Volume from being recycled and destroying a current backup. * * For Verify Jobs, we do not delete the last InitCatalog. * * For Restore Jobs there are no restrictions. */ int prune_jobs(UAContext *ua, CLIENT *client, int JobType) { struct del_ctx del; POOL_MEM query(PM_MESSAGE); utime_t now, period; CLIENT_DBR cr; char ed1[50], ed2[50]; db_lock(ua->db); memset(&cr, 0, sizeof(cr)); memset(&del, 0, sizeof(del)); bstrncpy(cr.Name, client->name(), sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { db_unlock(ua->db); return 0; } period = client->JobRetention; now = (utime_t)time(NULL); /* Drop any previous temporary tables still there */ drop_temp_tables(ua); /* Create temp tables and indicies */ if (!create_temp_tables(ua)) { goto bail_out; } /* * Select all files that are older than the JobRetention period * and stuff them into the "DeletionCandidates" table. */ edit_int64(now - period, ed1); Mmsg(query, insert_delcand, (char)JobType, ed1, edit_int64(cr.ClientId, ed2)); if (!db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL)) { if (ua->verbose) { ua->error_msg("%s", db_strerror(ua->db)); } Dmsg0(050, "insert delcand failed\n"); goto bail_out; } del.max_ids = 100; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); del.PurgedFiles = (char *)malloc(del.max_ids); /* ed1 = JobTDate */ edit_int64(cr.ClientId, ed2); switch (JobType) { case JT_BACKUP: Mmsg(query, select_backup_del, ed1, ed2); break; case JT_RESTORE: Mmsg(query, select_restore_del, ed1, ed2); break; case JT_VERIFY: Mmsg(query, select_verify_del, ed1, ed2); break; case JT_ADMIN: Mmsg(query, select_admin_del, ed1, ed2); break; case JT_COPY: Mmsg(query, select_copy_del, ed1, ed2); break; case JT_MIGRATE: Mmsg(query, select_migrate_del, ed1, ed2); break; } Dmsg1(150, "Query=%s\n", query.c_str()); if (!db_sql_query(ua->db, query.c_str(), job_delete_handler, (void *)&del)) { ua->error_msg("%s", db_strerror(ua->db)); } purge_job_list_from_catalog(ua, del); if (del.num_del > 0) { ua->info_msg(_("Pruned %d %s for client %s from catalog.\n"), del.num_del, del.num_del==1?_("Job"):_("Jobs"), client->name()); } else if (ua->verbose) { ua->info_msg(_("No Jobs found to prune.\n")); } bail_out: drop_temp_tables(ua); db_unlock(ua->db); if (del.JobId) { free(del.JobId); } if (del.PurgedFiles) { free(del.PurgedFiles); } return 1; }
/* * Prune File records from the database. For any Job which * is older than the retention period, we unconditionally delete * all File records for that Job. This is simple enough that no * temporary tables are needed. We simply make an in memory list of * the JobIds meeting the prune conditions, then delete all File records * pointing to each of those JobIds. * * This routine assumes you want the pruning to be done. All checking * must be done before calling this routine. */ int prune_files(UAContext *ua, CLIENT *client) { struct del_ctx del; struct s_count_ctx cnt; POOL_MEM query(PM_MESSAGE); utime_t now, period; CLIENT_DBR cr; char ed1[50], ed2[50]; db_lock(ua->db); memset(&cr, 0, sizeof(cr)); memset(&del, 0, sizeof(del)); bstrncpy(cr.Name, client->hdr.name, sizeof(cr.Name)); if (!db_create_client_record(ua->jcr, ua->db, &cr)) { db_unlock(ua->db); return 0; } period = client->FileRetention; now = (utime_t)time(NULL); /* Select Jobs -- for counting */ Mmsg(query, count_select_job, edit_int64(now - period, ed1), edit_int64(cr.ClientId, ed2)); Dmsg3(050, "select now=%u period=%u sql=%s\n", (uint32_t)now, (uint32_t)period, query.c_str()); cnt.count = 0; if (!db_sql_query(ua->db, query.c_str(), del_count_handler, (void *)&cnt)) { ua->error_msg("%s", db_strerror(ua->db)); Dmsg0(050, "Count failed\n"); goto bail_out; } if (cnt.count == 0) { if (ua->verbose) { ua->warning_msg(_("No Files found to prune.\n")); } goto bail_out; } if (cnt.count < MAX_DEL_LIST_LEN) { del.max_ids = cnt.count + 1; } else { del.max_ids = MAX_DEL_LIST_LEN; } del.tot_ids = 0; del.JobId = (JobId_t *)malloc(sizeof(JobId_t) * del.max_ids); /* Now process same set but making a delete list */ Mmsg(query, select_job, edit_int64(now - period, ed1), edit_int64(cr.ClientId, ed2)); db_sql_query(ua->db, query.c_str(), file_delete_handler, (void *)&del); purge_files_from_job_list(ua, del); edit_uint64_with_commas(del.num_del, ed1); ua->info_msg(_("Pruned Files from %s Jobs for client %s from catalog.\n"), ed1, client->name()); bail_out: db_unlock(ua->db); if (del.JobId) { free(del.JobId); } return 1; }