+/* ------------------------ */
+
+static GSList *
+get_file_list(
+ int argc,
+ char **argv,
+ int allow_empty)
+{
+ GSList * file_list = NULL;
+ GSList * dumplist;
+ int flags;
+
+ flags = CMDLINE_PARSE_DATESTAMP;
+ if (allow_empty) flags |= CMDLINE_EMPTY_TO_WILDCARD;
+ dumplist = cmdline_parse_dumpspecs(argc, argv, flags);
+
+ file_list = cmdline_match_holding(dumplist);
+ dumpspec_list_free(dumplist);
+
+ return file_list;
+}
+
+/* Given a file header, find the history element in curinfo most likely
+ * corresponding to that dump (this is not an exact science).
+ *
+ * @param info: the info_t element for this DLE
+ * @param file: the header of the file
+ * @returns: index of the matching history element, or -1 if not found
+ */
+static int
+holding_file_find_history(
+ info_t *info,
+ dumpfile_t *file)
+{
+ int matching_hist_idx = -1;
+ int nhist;
+ int i;
+
+ /* Begin by trying to find the history element matching this dump.
+ * The datestamp on the dump is for the entire run of amdump, while the
+ * 'date' in the history element of 'info' is the time the dump itself
+ * began. A matching history element, then, is the earliest element
+ * with a 'date' equal to or later than the date of the dumpfile.
+ *
+ * We compare using formatted datestamps; even using seconds since epoch,
+ * we would still face timezone issues, and have to do a reverse (timezone
+ * to gmt) translation.
+ */
+
+ /* get to the end of the history list and search backward */
+ for (nhist = 0; info->history[nhist].level > -1; nhist++) /* empty loop */;
+ for (i = nhist-1; i > -1; i--) {
+ char *info_datestamp = get_timestamp_from_time(info->history[i].date);
+ int order = strcmp(file->datestamp, info_datestamp);
+ amfree(info_datestamp);
+
+ if (order <= 0) {
+ /* only a match if the levels are equal */
+ if (info->history[i].level == file->dumplevel) {
+ matching_hist_idx = i;
+ }
+ break;
+ }
+ }
+
+ return matching_hist_idx;
+}
+
+/* A holding file is 'outdated' if a subsequent dump of the same DLE was made
+ * at the same level or a lower leve; for example, a level 2 dump is outdated if
+ * there is a subsequent level 2, or a subsequent level 0.
+ *
+ * @param file: the header of the file
+ * @returns: true if the file is outdated
+ */
+static int
+holding_file_is_outdated(
+ dumpfile_t *file)
+{
+ info_t info;
+ int matching_hist_idx;
+
+ if (get_info(file->name, file->disk, &info) == -1) {
+ return 0; /* assume it's not outdated */
+ }
+
+ /* if the last level is less than the level of this dump, then
+ * it's outdated */
+ if (info.last_level < file->dumplevel)
+ return 1;
+
+ /* otherwise, we need to see if this dump is the last at its level */
+ matching_hist_idx = holding_file_find_history(&info, file);
+ if (matching_hist_idx == -1) {
+ return 0; /* assume it's not outdated */
+ }
+
+ /* compare the date of the history element with the most recent date
+ * for this level. If they match, then this is the last dump at this
+ * level, and we checked above for more recent lower-level dumps, so
+ * the dump is not outdated. */
+ if (info.history[matching_hist_idx].date ==
+ info.inf[info.history[matching_hist_idx].level].date) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static int
+remove_holding_file_from_catalog(
+ char *filename)
+{
+ static int warnings_printed; /* only print once per invocation */
+ dumpfile_t file;
+ info_t info;
+ int matching_hist_idx = -1;
+ history_t matching_hist; /* will be a copy */
+ int i;
+
+ if (!holding_file_get_dumpfile(filename, &file)) {
+ g_printf(_("Could not read holding file %s\n"), filename);
+ return 0;
+ }
+
+ if (get_info(file.name, file.disk, &info) == -1) {
+ g_printf(_("WARNING: No curinfo record for %s:%s\n"), file.name, file.disk);
+ dumpfile_free_data(&file);
+ return 1; /* not an error */
+ }
+
+ matching_hist_idx = holding_file_find_history(&info, &file);
+
+ if (matching_hist_idx == -1) {
+ g_printf(_("WARNING: No dump matching %s found in curinfo.\n"), filename);
+ dumpfile_free_data(&file);
+ return 1; /* not an error */
+ }
+
+ /* make a copy */
+ matching_hist = info.history[matching_hist_idx];
+
+ /* Remove the history element itself before doing the stats */
+ for (i = matching_hist_idx; i < NB_HISTORY; i++) {
+ info.history[i] = info.history[i+1];
+ }
+ info.history[NB_HISTORY].level = -1;
+
+ /* Remove stats for that history element, if necessary. Doing so
+ * will result in an inconsistent set of backups, so we warn the
+ * user and adjust last_level to make the next dump get us a
+ * consistent picture. */
+ if (matching_hist.date == info.inf[matching_hist.level].date) {
+ /* search for an earlier dump at this level */
+ for (i = matching_hist_idx; info.history[i].level > -1; i++) {
+ if (info.history[i].level == matching_hist.level)
+ break;
+ }
+
+ if (info.history[i].level < 0) {
+ /* not found => zero it out */
+ info.inf[matching_hist.level].date = (time_t)-1; /* flag as not set */
+ info.inf[matching_hist.level].label[0] = '\0';
+ } else {
+ /* found => reconstruct stats as best we can */
+ info.inf[matching_hist.level].size = info.history[i].size;
+ info.inf[matching_hist.level].csize = info.history[i].csize;
+ info.inf[matching_hist.level].secs = info.history[i].secs;
+ info.inf[matching_hist.level].date = info.history[i].date;
+ info.inf[matching_hist.level].filenum = 0; /* we don't know */
+ info.inf[matching_hist.level].label[0] = '\0'; /* we don't know */
+ }
+
+ /* set last_level to the level we just deleted, and set command
+ * appropriately to make sure planner does a new dump at this level
+ * or lower */
+ info.last_level = matching_hist.level;
+ if (info.last_level == 0) {
+ g_printf(_("WARNING: Deleting the most recent full dump; forcing a full dump at next run.\n"));
+ SET(info.command, FORCE_FULL);
+ } else {
+ g_printf(_("WARNING: Deleting the most recent level %d dump; forcing a level %d dump or \nWARNING: lower at next run.\n"),
+ info.last_level, info.last_level);
+ SET(info.command, FORCE_NO_BUMP);
+ }
+
+ /* Search for and display any subsequent runs that depended on this one */
+ warnings_printed = 0;
+ for (i = matching_hist_idx-1; i >= 0; i--) {
+ char *datestamp;
+ if (info.history[i].level <= matching_hist.level) break;
+
+ datestamp = get_timestamp_from_time(info.history[i].date);
+ g_printf(_("WARNING: Level %d dump made %s can no longer be accurately restored.\n"),
+ info.history[i].level, datestamp);
+ amfree(datestamp);
+
+ warnings_printed = 1;
+ }
+ if (warnings_printed)
+ g_printf(_("WARNING: (note, dates shown above are for dumps, and may be later than the\nWARNING: corresponding run date)\n"));
+ }
+
+ /* recalculate consecutive_runs based on the history: find the first run
+ * at this level, and then count the consecutive runs at that level. This
+ * number may be zero (if we just deleted the last run at this level) */
+ info.consecutive_runs = 0;
+ for (i = 0; info.history[i].level >= 0; i++) {
+ if (info.history[i].level == info.last_level) break;
+ }
+ while (info.history[i+info.consecutive_runs].level == info.last_level)
+ info.consecutive_runs++;
+
+ /* this function doesn't touch the performance stats */
+
+ /* write out the changes */
+ if (put_info(file.name, file.disk, &info) == -1) {
+ g_printf(_("Could not write curinfo record for %s:%s\n"), file.name, file.disk);
+ dumpfile_free_data(&file);
+ return 0;
+ }
+
+ dumpfile_free_data(&file);
+ return 1;
+}
+
+void
+holding(
+ int argc,
+ char ** argv)
+{
+ GSList *file_list;
+ GSList *li;
+ enum { HOLDING_USAGE, HOLDING_LIST, HOLDING_DELETE } action = HOLDING_USAGE;
+ int long_list = 0;
+ int outdated_list = 0;
+ dumpfile_t file;
+
+ if (argc < 4)
+ action = HOLDING_USAGE;
+ else if (strcmp(argv[3], "list") == 0 && argc >= 4)
+ action = HOLDING_LIST;
+ else if (strcmp(argv[3], "delete") == 0 && argc > 4)
+ action = HOLDING_DELETE;
+
+ switch (action) {
+ case HOLDING_USAGE:
+ g_fprintf(stderr,
+ _("%s: expecting \"holding list [-l] [-d]\" or \"holding delete <host> [ .. ]\"\n"),
+ get_pname());
+ usage();
+ return;
+
+ case HOLDING_LIST:
+ argc -= 4; argv += 4;
+ while (argc && argv[0][0] == '-') {
+ switch (argv[0][1]) {
+ case 'l':
+ long_list = 1;
+ break;
+ case 'd': /* have to use '-d', and not '-o', because of parse_config */
+ outdated_list = 1;
+ break;
+ default:
+ g_fprintf(stderr, _("Unknown option -%c\n"), argv[0][1]);
+ usage();
+ return;
+ }
+ argc--; argv++;
+ }
+
+ /* header */
+ if (long_list) {
+ g_printf("%-10s %-2s %-4s %s\n",
+ _("size (kB)"), _("lv"), _("outd"), _("dump specification"));
+ }
+
+ file_list = get_file_list(argc, argv, 1);
+ for (li = file_list; li != NULL; li = li->next) {
+ char *dumpstr;
+ int is_outdated;
+
+ if (!holding_file_get_dumpfile((char *)li->data, &file)) {
+ g_fprintf(stderr, _("Error reading %s\n"), (char *)li->data);
+ continue;
+ }
+
+ is_outdated = holding_file_is_outdated(&file);
+
+ dumpstr = cmdline_format_dumpspec_components(file.name, file.disk, file.datestamp, NULL);
+ /* only print this entry if we're printing everything, or if it's outdated and
+ * we're only printing outdated files (-o) */
+ if (!outdated_list || is_outdated) {
+ if (long_list) {
+ g_printf("%-10lld %-2d %-4s %s\n",
+ (long long)holding_file_size((char *)li->data, 0),
+ file.dumplevel,
+ is_outdated? " *":"",
+ dumpstr);
+ } else {
+ g_printf("%s\n", dumpstr);
+ }
+ }
+ amfree(dumpstr);
+ dumpfile_free_data(&file);
+ }
+ g_slist_free_full(file_list);
+ break;
+
+ case HOLDING_DELETE:
+ argc -= 4; argv += 4;
+
+ file_list = get_file_list(argc, argv, 0);
+ for (li = file_list; li != NULL; li = li->next) {
+ g_fprintf(stderr, _("Deleting '%s'\n"), (char *)li->data);
+ /* remove it from the catalog */
+ if (!remove_holding_file_from_catalog((char *)li->data))
+ exit(1);
+
+ /* unlink it */
+ if (!holding_file_unlink((char *)li->data)) {
+ error(_("Could not delete '%s'"), (char *)li->data);
+ }
+ }
+ g_slist_free_full(file_list);
+ break;
+ }
+}
+
+