Imported Upstream version 2.5.2p1
[debian/amanda] / server-src / amadmin.c
index d1ea05784dbeba3b5bc7c60fdece7c29b4624642..526434c895efca54eff374e024a7d8ba730dfd99 100644 (file)
  *                        University of Maryland at College Park
  */
 /*
- * $Id: amadmin.c,v 1.124.2.1 2006/11/01 14:45:39 martinea Exp $
+ * $Id: amadmin.c,v 1.124 2006/07/26 15:17:37 martinea Exp $
  *
  * controlling process for the Amanda backup system
  */
 #include "amanda.h"
+#include "cmdline.h"
 #include "conffile.h"
 #include "diskfile.h"
 #include "tapefile.h"
@@ -38,6 +39,7 @@
 #include "version.h"
 #include "holding.h"
 #include "find.h"
+#include "util.h"
 
 disklist_t diskq;
 
@@ -60,6 +62,7 @@ void info_one(disk_t *dp);
 void due(int argc, char **argv);
 void due_one(disk_t *dp);
 void find(int argc, char **argv);
+void holding(int argc, char **argv);
 void delete(int argc, char **argv);
 void delete_one(disk_t *dp);
 void balance(int argc, char **argv);
@@ -108,6 +111,8 @@ static const struct {
        " <tapelabel> ...\t # never re-use this tape." },
     { "find", find,
        " [<hostname> [<disks>]* ]*\t # Show which tapes these dumps are on." },
+    { "holding", holding,
+       " {list [ -l ] |delete} [ <hostname> [ <disk> [ <datestamp> [ .. ] ] ] ]+\t # Show or delete holding disk contents." },
     { "delete", delete,
        " [<hostname> [<disks>]* ]+ # Delete from database." },
     { "info", info,
@@ -156,7 +161,7 @@ main(
 
     erroutput_type = ERR_INTERACTIVE;
 
-    parse_server_conf(argc, argv, &new_argc, &new_argv);
+    parse_conf(argc, argv, &new_argc, &new_argv);
 
     if(new_argc < 3) usage();
 
@@ -1170,6 +1175,256 @@ find(
 }
 
 
+/* ------------------------ */
+
+static sl_t *
+get_file_list(
+    int argc,
+    char **argv,
+    int allow_empty)
+{
+    sl_t * file_list = NULL;
+    dumpspec_list_t * dumplist;
+
+    if (argc > 0) {
+        dumplist = cmdline_parse_dumpspecs(argc, argv);
+        if (!dumplist) {
+            fprintf(stderr, _("Could not get dump list\n"));
+            return NULL;
+        }
+
+        file_list = cmdline_match_holding(dumplist);
+        dumpspec_free_list(dumplist);
+    } else if (allow_empty) {
+        /* just list all of them */
+        file_list = holding_get_files(NULL, NULL, 1);
+    }
+
+    return file_list;
+}
+
+static int
+remove_holding_file_from_catalog(
+    char *filename)
+{
+    static int warnings_printed; /* only print once per invocation */
+    char *host;
+    char *disk;
+    int level;
+    char *datestamp;
+    info_t info;
+    int matching_hist_idx = -1;
+    history_t matching_hist; /* will be a copy */
+    int nhist;
+    int i;
+
+    if (!holding_file_read_header(filename, &host, &disk, &level, &datestamp)) {
+        printf(_("Could not read holding file %s\n"), filename);
+        return 0;
+    }
+
+    if (get_info(host, disk, &info) == -1) {
+           printf(_("WARNING: No curinfo record for %s:%s\n"), host, disk);
+           return 1; /* not an error */
+    }
+
+    /* 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 = construct_timestamp(&info.history[i].date);
+        int order = strcmp(datestamp, info_datestamp);
+        amfree(info_datestamp);
+
+        if (order <= 0) {
+            /* only a match if the levels are equal */
+            if (info.history[i].level == level) {
+                matching_hist_idx = i;
+                matching_hist = info.history[matching_hist_idx];
+            }
+            break;
+        }
+    }
+
+    if (matching_hist_idx == -1) {
+        printf(_("WARNING: No dump matching %s found in curinfo.\n"), filename);
+        return 1; /* not an error */
+    }
+
+    /* 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) {
+            printf(_("WARNING: Deleting the most recent full dump; forcing a full dump at next run.\n"));
+            SET(info.command, FORCE_FULL);
+        } else {
+            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 = construct_timestamp(&info.history[i].date);
+            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)
+            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(host, disk, &info) == -1) {
+           printf(_("Could not write curinfo record for %s:%s\n"), host, disk);
+           return 0;
+    }
+
+    return 1;
+}
+void
+holding(
+    int                argc,
+    char **    argv)
+{
+    sl_t *file_list;
+    sle_t *h;
+    enum { HOLDING_USAGE, HOLDING_LIST, HOLDING_DELETE } action = HOLDING_USAGE;
+    char *host;
+    char *disk;
+    char *datestamp;
+    int level;
+    int long_list = 0;
+
+    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;
+
+    holding_set_verbosity(1);
+
+    switch (action) {
+        case HOLDING_USAGE:
+            fprintf(stderr,
+                    _("%s: expecting \"holding list [-l]\" or \"holding delete <host> [ .. ]\"\n"),
+                    get_pname());
+            usage();
+            return;
+
+        case HOLDING_LIST:
+            argc -= 4; argv += 4;
+            if (argc && strcmp(argv[0], "-l") == 0) {
+                argc--; argv++;
+                long_list = 1;
+            }
+
+            file_list = get_file_list(argc, argv, 1);
+            if (long_list) {
+                printf("%-10s %-2s %s\n", "size (kB)", "lv", "dump specification");
+            }
+            for (h = file_list->first; h != NULL; h = h->next) {
+                char *dumpstr;
+                if (!holding_file_read_header(h->name, &host, &disk, &level, &datestamp)) {
+                    fprintf(stderr, _("Error reading %s\n"), h->name);
+                    continue;
+                }
+
+                dumpstr = cmdline_format_dumpspec_components(host, disk, datestamp);
+                if (long_list) {
+                    printf("%-10"OFF_T_RFMT" %-2d %s\n", 
+                        (OFF_T_FMT_TYPE)holding_file_size(h->name, 0), level, dumpstr);
+                } else {
+                    printf("%s\n", dumpstr);
+                }
+                amfree(dumpstr);
+            }
+            free_sl(file_list);
+            break;
+            
+        case HOLDING_DELETE:
+            argc -= 4; argv += 4;
+
+            file_list = get_file_list(argc, argv, 0);
+            for (h = file_list->first; h != NULL; h = h->next) {
+                fprintf(stderr, _("Deleting '%s'\n"), h->name);
+                /* remove it from the catalog */
+                if (!remove_holding_file_from_catalog(h->name))
+                    exit(1);
+
+                /* unlink it */
+                if (!holding_file_unlink(h->name)) {
+                    /* holding_file_unlink printed an error message */
+                    exit(1);
+                }
+            }
+            free_sl(file_list);
+            break;
+    }
+}
+
+
 /* ------------------------ */
 
 
@@ -1351,13 +1606,10 @@ import_db(
     ch = *s++;
 
     hdr = "version";
-#define sc "CURINFO Version"
-    if(strncmp(s - 1, sc, SIZEOF(sc)-1) != 0) {
+    if(strncmp_const_skip(s - 1, "CURINFO Version", s, ch) != 0) {
        goto bad_header;
     }
-    s += SIZEOF(sc)-1;
     ch = *s++;
-#undef sc
     skip_whitespace(s, ch);
     if(ch == '\0'
        || sscanf(s - 1, "%d.%d.%d", &vers_maj, &vers_min, &vers_patch) != 3) {
@@ -1385,13 +1637,10 @@ import_db(
 
     hdr = "CONF";
     skip_whitespace(s, ch);                    /* find the org keyword */
-#define sc "CONF"
-    if(ch == '\0' || strncmp(s - 1, sc, SIZEOF(sc)-1) != 0) {
+    if(ch == '\0' || strncmp_const_skip(s - 1, "CONF", s, ch) != 0) {
        goto bad_header;
     }
-    s += SIZEOF(sc)-1;
     ch = *s++;
-#undef sc
 
     hdr = "org";
     skip_whitespace(s, ch);                    /* find the org string */
@@ -1467,11 +1716,7 @@ import_one(void)
     ch = *s++;
 
     skip_whitespace(s, ch);
-#define sc "host:"
-    if(ch == '\0' || strncmp(s - 1, sc, SIZEOF(sc)-1) != 0) goto parse_err;
-    s += SIZEOF(sc)-1;
-    ch = s[-1];
-#undef sc
+    if(ch == '\0' || strncmp_const_skip(s - 1, "host:", s, ch) != 0) goto parse_err;
     skip_whitespace(s, ch);
     if(ch == '\0') goto parse_err;
     fp = s-1;
@@ -1488,11 +1733,7 @@ import_one(void)
       ch = *s++;
       skip_whitespace(s, ch);
     }
-#define sc "disk:"
-    if(strncmp(s - 1, sc, SIZEOF(sc)-1) != 0) goto parse_err;
-    s += SIZEOF(sc)-1;
-    ch = s[-1];
-#undef sc
+    if(strncmp_const_skip(s - 1, "disk:", s, ch) != 0) goto parse_err;
     skip_whitespace(s, ch);
     if(ch == '\0') goto parse_err;
     fp = s-1;
@@ -1549,11 +1790,11 @@ import_one(void)
     while(1) {
        amfree(line);
        if((line = impget_line()) == NULL) goto shortfile_err;
-       if(strncmp(line, "//", 2) == 0) {
+       if(strncmp_const(line, "//") == 0) {
            /* end of record */
            break;
        }
-       if(strncmp(line, "history:", 8) == 0) {
+       if(strncmp_const(line, "history:") == 0) {
            /* end of record */
            break;
        }
@@ -1563,13 +1804,9 @@ import_one(void)
        ch = *s++;
 
        skip_whitespace(s, ch);
-#define sc "stats:"
-       if(ch == '\0' || strncmp(s - 1, sc, SIZEOF(sc)-1) != 0) {
+       if(ch == '\0' || strncmp_const_skip(s - 1, "stats:", s, ch) != 0) {
            goto parse_err;
        }
-       s += SIZEOF(sc)-1;
-       ch = s[-1];
-#undef sc
 
        skip_whitespace(s, ch);
        if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
@@ -1647,13 +1884,10 @@ import_one(void)
        memset(&onehistory, 0, SIZEOF(onehistory));
        s = line;
        ch = *s++;
-#define sc "history:"
-       if(strncmp(line, sc, SIZEOF(sc)-1) != 0) {
+       if(strncmp_const_skip(line, "history:", s, ch) != 0) {
            break;
        }
-       s += SIZEOF(sc)-1;
-       ch = s[-1];
-#undef sc
+
        skip_whitespace(s, ch);
        if(ch == '\0' || sscanf((s - 1), "%d", &onehistory.level) != 1) {
            break;
@@ -1828,7 +2062,7 @@ disklist_one(
        printf("INCRONLY\n");
        break;
     }
-
+    printf("        ignore %s\n", (dp->ignore? "YES" : "NO"));
     printf("        estimate ");
     switch(dp->estimate) {
     case ES_CLIENT:
@@ -1853,10 +2087,10 @@ disklist_one(
     case COMP_BEST:
        printf("CLIENT BEST\n");
        break;
-    case COMP_SERV_FAST:
+    case COMP_SERVER_FAST:
        printf("SERVER FAST\n");
        break;
-    case COMP_SERV_BEST:
+    case COMP_SERVER_BEST:
        printf("SERVER BEST\n");
        break;
     }
@@ -1900,7 +2134,6 @@ disklist_one(
     printf("        record %s\n", (dp->record? "YES" : "NO"));
     printf("        index %s\n", (dp->index? "YES" : "NO"));
     printf("        starttime %04d\n", (int)dp->starttime);
-   
     if(dp->tape_splitsize > (off_t)0) {
        printf("        tape_splitsize " OFF_T_FMT "\n",
               (OFF_T_FMT_TYPE)dp->tape_splitsize);