void db_list_joblog_records(JCR *jcr, B_DB *mdb, JobId_t JobId, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
int db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, DB_LIST_HANDLER *sendit, void *ctx, int verbose, e_list_type type);
void db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *jobids, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
/* sql_update.c */
bool db_update_job_start_record(JCR *jcr, B_DB *db, JOB_DBR *jr);
"FROM Client,Job,JobMedia,Media,FileSet WHERE Client.ClientId=%s "
"AND Job.ClientId=%s "
"AND Job.StartTime<'%s' "
- "AND Level='F' AND JobStatus='T' AND Type='B' "
+ "AND Level='F' AND JobStatus='T' "
"AND JobMedia.JobId=Job.JobId "
"AND Media.Enabled=1 "
"AND JobMedia.MediaId=Media.MediaId "
const char *uar_full =
"INSERT INTO temp SELECT Job.JobId,Job.JobTDate,"
- "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
- "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
- "FROM temp1,Job,JobMedia,Media WHERE temp1.JobId=Job.JobId "
- "AND Level='F' AND JobStatus='T' AND Type='B' "
- "AND Media.Enabled=1 "
- "AND JobMedia.JobId=Job.JobId "
- "AND JobMedia.MediaId=Media.MediaId";
+ "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
+ "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
+ "FROM temp1,Job,JobMedia,Media "
+ "WHERE temp1.JobId=Job.JobId "
+ "AND Level='F' AND JobStatus='T' "
+ "AND Media.Enabled=1 "
+ "AND JobMedia.JobId=Job.JobId "
+ "AND JobMedia.MediaId=Media.MediaId";
const char *uar_dif =
"INSERT INTO temp SELECT Job.JobId,Job.JobTDate,Job.ClientId,"
"AND JobMedia.JobId=Job.JobId "
"AND Media.Enabled=1 "
"AND JobMedia.MediaId=Media.MediaId "
- "AND Job.Level='D' AND JobStatus='T' AND Type='B' "
+ "AND Job.Level='D' AND JobStatus='T' "
"AND Job.FileSetId=FileSet.FileSetId "
"AND FileSet.FileSet='%s' "
"%s"
"AND Media.Enabled=1 "
"AND JobMedia.JobId=Job.JobId "
"AND JobMedia.MediaId=Media.MediaId "
- "AND Job.Level='I' AND JobStatus='T' AND Type='B' "
+ "AND Job.Level='I' AND JobStatus='T' "
"AND Job.FileSetId=FileSet.FileSetId "
"AND FileSet.FileSet='%s' "
"%s";
}
+void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
+ DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
+{
+ POOL_MEM str_limit(PM_MESSAGE);
+ POOL_MEM str_jobids(PM_MESSAGE);
+
+ if (limit > 0) {
+ Mmsg(str_limit, " LIMIT %d", limit);
+ }
+
+ if (JobIds && JobIds[0]) {
+ Mmsg(str_jobids, " AND (C.PriorJobId IN (%s) OR C.JobId IN (%s)) ",
+ JobIds, JobIds);
+ }
+
+ db_lock(mdb);
+ Mmsg(mdb->cmd,
+ "SELECT DISTINCT C.PriorJobId AS JobId, C.Job, "
+ "C.JobId AS CopyJobId, M.MediaType "
+ "FROM Job AS C "
+ "JOIN JobMedia USING (JobId) "
+ "JOIN Media AS M USING (MediaId) "
+ "WHERE C.Type = '%c' %s ORDER BY C.PriorJobId DESC %s",
+ (char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
+
+ if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ goto bail_out;
+ }
+
+ list_result(jcr, mdb, sendit, ctx, type);
+
+ sql_free_result(mdb);
+
+bail_out:
+ db_unlock(mdb);
+}
+
void db_list_joblog_records(JCR *jcr, B_DB *mdb, uint32_t JobId,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
{
/*
* If we terminated a copy normally:
* - copy any Log records to the new JobId
+ * - set type="Job Copy" for the new job
*/
if (jcr->get_JobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
/* Copy JobLog to new JobId */
Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
"SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
- new_jobid, old_jobid);
+ edit_uint64(mig_jcr->jr.JobId, ec7), old_jobid);
+ db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
+ Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
+ (char)JT_JOB_COPY, ec7);
db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
}
{ NT_("exit"), quit_cmd, _("exit = quit"), false},
{ NT_("gui"), gui_cmd, _("gui [on|off] -- non-interactive gui mode"), false},
{ NT_("help"), help_cmd, _("print this command"), false},
- { NT_("list"), list_cmd, _("list [pools | jobs | jobtotals | media <pool=pool-name> | files <jobid=nn>]; from catalog"), true},
+ { NT_("list"), list_cmd, _("list [pools | jobs | jobtotals | media <pool=pool-name> | files <jobid=nn> | copies <jobid=nn>]; from catalog"), true},
{ NT_("label"), label_cmd, _("label a tape"), false},
{ NT_("llist"), llist_cmd, _("full or long list like list command"), true},
{ NT_("messages"), messagescmd, _("messages"), false},
* list clients - list clients
* list nextvol job=xx - list the next vol to be used by job
* list nextvolume job=xx - same as above.
+ * list copies jobid=x,y,z
*
*/
}
}
list_nextvol(ua, n);
+ } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
+ char *jobids=NULL;
+ uint32_t limit=0;
+ for (j=i+1; j<ua->argc; j++) {
+ if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
+ if (is_a_number_list(ua->argv[j])) {
+ jobids = ua->argv[j];
+ }
+ } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
+ limit = atoi(ua->argv[j]);
+ }
+ }
+ db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
} else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
|| strcasecmp(ua->argk[i], NT_("days")) == 0) {
/* Ignore it */
}
}
+/*
+ * Change the type of the next copy job to backup.
+ * We need to upgrade the next copy of a normal job,
+ * and also upgrade the next copy when the normal job
+ * already have been purged.
+ *
+ * JobId: 1 PriorJobId: 0 (original)
+ * JobId: 2 PriorJobId: 1 (first copy)
+ * JobId: 3 PriorJobId: 1 (second copy)
+ *
+ * JobId: 2 PriorJobId: 1 (first copy, now regular backup)
+ * JobId: 3 PriorJobId: 1 (second copy)
+ *
+ * => Search through PriorJobId in jobid and
+ * PriorJobId in PriorJobId (jobid)
+ */
+void upgrade_copies(UAContext *ua, char *jobs)
+{
+ POOL_MEM query(PM_MESSAGE);
+
+ db_lock(ua->db);
+ /* Do it in two times for mysql */
+ Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
+ "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
+ "WHERE Type='%c' "
+ "AND ( PriorJobId IN (%s) "
+ "OR "
+ " PriorJobId IN ( "
+ "SELECT PriorJobId "
+ "FROM Job "
+ "WHERE JobId IN (%s) "
+ " AND Type='B' "
+ ") "
+ ") "
+ "GROUP BY PriorJobId ", /* one result per copy */
+ JT_JOB_COPY, jobs, jobs);
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
+ /* Now upgrade first copy to Backup */
+ Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
+ "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
+
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
+ Mmsg(query, "DROP TABLE cpy_tmp");
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
+ db_unlock(ua->db);
+ Dmsg1(00, "Upgrade copies Log sql=%s\n", query.c_str());
+}
+
/*
* Remove all records from catalog for a list of JobIds
*/
db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
+ upgrade_copies(ua, jobs);
+
/* Now remove the Job record itself */
Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+
Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
}
-
void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
{} /* ***FIXME*** implement */
"add_suffix", /* 17 */
"regexwhere", /* 18 */
"restoreclient", /* 19 */
+ "copies", /* 20 */
NULL
};
bool ok = false;
FILESET_DBR fsr;
CLIENT_DBR cr;
+ POOL_MEM other_filter(PM_MESSAGE);
+ POOL_MEM temp_filter(PM_MESSAGE);
char fileset_name[MAX_NAME_LENGTH];
char ed1[50], ed2[50];
- char pool_select[MAX_NAME_LENGTH];
int i;
/* Create temp tables */
}
/* If Pool specified, add PoolId specification */
- pool_select[0] = 0;
if (rx->pool) {
POOL_DBR pr;
memset(&pr, 0, sizeof(pr));
bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
- bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
- edit_int64(pr.PoolId, ed1));
+ Mmsg(other_filter, " AND Media.PoolId=%s ",
+ edit_int64(pr.PoolId, ed1));
} else {
ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
}
}
+ /* include copies or not in job selection */
+ if (find_arg(ua, NT_("copies")) > 0) {
+ Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ",
+ other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
+ } else {
+ Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
+ (char)JT_BACKUP);
+ }
+ pm_strcpy(other_filter, temp_filter.c_str());
/* Find JobId of last Full backup for this client, fileset */
edit_int64(cr.ClientId, ed1);
Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
- pool_select);
+ other_filter.c_str());
+ Dmsg1(0, "sql=%s\n", rx->query);
if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
ua->error_msg("%s\n", db_strerror(ua->db));
goto bail_out;
/* Now find most recent Differental Job after Full save, if any */
Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
ua->warning_msg("%s\n", db_strerror(ua->db));
}
/* Now update JobTDate to lock onto Differental, if any */
rx->JobTDate = 0;
+ Dmsg1(0, "sql=%s\n", rx->query);
if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
ua->warning_msg("%s\n", db_strerror(ua->db));
}
/* Now find all Incremental Jobs after Full/dif save */
Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
+ Dmsg1(0, "sql=%s\n", rx->query);
if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
ua->warning_msg("%s\n", db_strerror(ua->db));
}
}
if (rx->JobIds[0] != 0) {
+ /* Display a list of all copies */
+ db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, prtit, ua, HORZ_LIST);
/* Display a list of Jobs selected for this restore */
db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
ok = true;
#define JT_MIGRATED_JOB 'M' /* A previous backup job that was migrated */
#define JT_VERIFY 'V' /* Verify Job */
#define JT_RESTORE 'R' /* Restore Job */
-#define JT_CONSOLE 'c' /* console program */
+#define JT_CONSOLE 'U' /* console program */
#define JT_SYSTEM 'I' /* internal system "job" */
#define JT_ADMIN 'D' /* admin job */
#define JT_ARCHIVE 'A' /* Archive Job */
-#define JT_COPY 'C' /* Copy Job */
+#define JT_JOB_COPY 'C' /* Copy of a Job */
+#define JT_COPY 'c' /* Copy Job */
#define JT_MIGRATE 'g' /* Migration Job */
#define JT_SCAN 'S' /* Scan Job */
return digit_seen && *n==0;
}
+/*
+ * Check if specified string is a list of number or not
+ */
+bool is_a_number_list(const char *n)
+{
+ bool previous_digit = false;
+ bool digit_seen = false;
+ while (*n) {
+ if (B_ISDIGIT(*n)) {
+ previous_digit=true;
+ digit_seen = true;
+ } else if (*n == ',' && previous_digit) {
+ previous_digit = false;
+ } else {
+ return false;
+ }
+ n++;
+ }
+ return digit_seen && *n==0;
+}
+
/*
* Check if the specified string is an integer
*/
bool size_to_uint64(char *str, int str_len, uint64_t *rtn_value);
char *edit_utime (utime_t val, char *buf, int buf_len);
bool is_a_number (const char *num);
+bool is_a_number_list (const char *n);
bool is_an_integer (const char *n);
bool is_name_valid (char *name, POOLMEM **msg);
case JT_COPY:
str = _("Copy");
break;
+ case JT_JOB_COPY:
+ str = _("Job Copy");
+ break;
case JT_CONSOLE:
str = _("Console");
break;
General:
20Dec08
+ebl Work on copy jobs
+ - Add "list copies" command
+ - Add JT_JOB_COPY type for job copies
+ - Don't allow copy jobs in automatic restore
+ - Promote next copy job as backup when original job is deleted
kes Closed bug #1207 -- 2.4.4-b1 strange volume/device handling
kes Closed bug #1204 -- Undescriptive help options
kes Closed bug #1202 -- Revise documentation