]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/dird/run_conf.c
Implement MaxVirtualFullInterval
[bacula/bacula] / bacula / src / dird / run_conf.c
index 0d31987a2310a199268a79a0dd754962c47ca09e..c4a41578a6101cac935de83821d9eef7e72c4465 100644 (file)
@@ -1,3 +1,21 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2015 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.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is 
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
 /*
  *
  *  Configuration parser for Director Run Configuration
  *
  *     Kern Sibbald, May MM
  *
- *     Version $Id$
- */
-/*
-   Copyright (C) 2000-2004 Kern Sibbald and John Walker
-
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License as
-   published by the Free Software Foundation; either version 2 of
-   the License, or (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-   General Public License for more details.
-
-   You should have received a copy of the GNU General Public
-   License along with this program; if not, write to the Free
-   Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
-   MA 02111-1307, USA.
-
  */
 
 #include "bacula.h"
 #include "dird.h"
 
+#if defined(_MSC_VER)
+extern "C" { // work around visual compiler mangling variables
+   extern URES res_all;
+}
+#else
 extern URES res_all;
-extern struct s_jl joblevels[];
+#endif
+extern s_jl joblevels[];
 
 /* Forward referenced subroutines */
 
@@ -47,77 +51,81 @@ enum e_state {
    s_weekly,
    s_monthly,
    s_hourly,
-   s_wom,                          /* 1st, 2nd, ...*/
-   s_woy                           /* week of year w00 - w53 */
-};  
+   s_wom,                           /* 1st, 2nd, ...*/
+   s_woy,                           /* week of year w00 - w53 */
+   s_ldom                           /* last day of month */
+};
 
 struct s_keyw {
-  const char *name;                          /* keyword */
-  enum e_state state;                /* parser state */
-  int code;                          /* state value */
+  const char *name;                   /* keyword */
+  enum e_state state;                 /* parser state */
+  int code;                           /* state value */
 };
 
 /* Keywords understood by parser */
 static struct s_keyw keyw[] = {
-  {N_("on"),         s_none,    0},
-  {N_("at"),         s_at,      0},
-
-  {N_("sun"),        s_wday,    0},
-  {N_("mon"),        s_wday,    1},
-  {N_("tue"),        s_wday,    2},
-  {N_("wed"),        s_wday,    3},
-  {N_("thu"),        s_wday,    4},
-  {N_("fri"),        s_wday,    5},
-  {N_("sat"),        s_wday,    6},
-  {N_("jan"),        s_month,   0},
-  {N_("feb"),        s_month,   1},
-  {N_("mar"),        s_month,   2},
-  {N_("apr"),        s_month,   3},
-  {N_("may"),        s_month,   4},
-  {N_("jun"),        s_month,   5},
-  {N_("jul"),        s_month,   6},
-  {N_("aug"),        s_month,   7},
-  {N_("sep"),        s_month,   8},
-  {N_("oct"),        s_month,   9},
-  {N_("nov"),        s_month,  10},
-  {N_("dec"),        s_month,  11},
-
-  {N_("sunday"),     s_wday,    0},
-  {N_("monday"),     s_wday,    1},
-  {N_("tuesday"),    s_wday,    2},
-  {N_("wednesday"),  s_wday,    3},
-  {N_("thursday"),   s_wday,    4},
-  {N_("friday"),     s_wday,    5},
-  {N_("saturday"),   s_wday,    6},
-  {N_("january"),    s_month,   0},
-  {N_("february"),   s_month,   1},
-  {N_("march"),      s_month,   2},
-  {N_("april"),      s_month,   3},
-  {N_("june"),       s_month,   5},
-  {N_("july"),       s_month,   6},
-  {N_("august"),     s_month,   7},
-  {N_("september"),  s_month,   8},
-  {N_("october"),    s_month,   9},
-  {N_("november"),   s_month,  10},
-  {N_("december"),   s_month,  11},
-
-  {N_("daily"),      s_daily,   0},
-  {N_("weekly"),     s_weekly,  0},
-  {N_("monthly"),    s_monthly, 0},
-  {N_("hourly"),     s_hourly,  0},
-
-  {N_("1st"),        s_wom,     0},
-  {N_("2nd"),        s_wom,     1},
-  {N_("3rd"),        s_wom,     2},
-  {N_("4th"),        s_wom,     3},
-  {N_("5th"),        s_wom,     4},
-
-  {N_("first"),      s_wom,     0},
-  {N_("second"),     s_wom,     1},
-  {N_("third"),      s_wom,     2},
-  {N_("fourth"),     s_wom,     3},
-  {N_("fifth"),      s_wom,     4},
-  {NULL,        s_none,    0}
+  {NT_("on"),         s_none,    0},
+  {NT_("at"),         s_at,      0},
+  {NT_("lastday"),    s_ldom,    0},
+
+  {NT_("sun"),        s_wday,    0},
+  {NT_("mon"),        s_wday,    1},
+  {NT_("tue"),        s_wday,    2},
+  {NT_("wed"),        s_wday,    3},
+  {NT_("thu"),        s_wday,    4},
+  {NT_("fri"),        s_wday,    5},
+  {NT_("sat"),        s_wday,    6},
+  {NT_("jan"),        s_month,   0},
+  {NT_("feb"),        s_month,   1},
+  {NT_("mar"),        s_month,   2},
+  {NT_("apr"),        s_month,   3},
+  {NT_("may"),        s_month,   4},
+  {NT_("jun"),        s_month,   5},
+  {NT_("jul"),        s_month,   6},
+  {NT_("aug"),        s_month,   7},
+  {NT_("sep"),        s_month,   8},
+  {NT_("oct"),        s_month,   9},
+  {NT_("nov"),        s_month,  10},
+  {NT_("dec"),        s_month,  11},
+
+  {NT_("sunday"),     s_wday,    0},
+  {NT_("monday"),     s_wday,    1},
+  {NT_("tuesday"),    s_wday,    2},
+  {NT_("wednesday"),  s_wday,    3},
+  {NT_("thursday"),   s_wday,    4},
+  {NT_("friday"),     s_wday,    5},
+  {NT_("saturday"),   s_wday,    6},
+  {NT_("january"),    s_month,   0},
+  {NT_("february"),   s_month,   1},
+  {NT_("march"),      s_month,   2},
+  {NT_("april"),      s_month,   3},
+  {NT_("june"),       s_month,   5},
+  {NT_("july"),       s_month,   6},
+  {NT_("august"),     s_month,   7},
+  {NT_("september"),  s_month,   8},
+  {NT_("october"),    s_month,   9},
+  {NT_("november"),   s_month,  10},
+  {NT_("december"),   s_month,  11},
+
+  {NT_("daily"),      s_daily,   0},
+  {NT_("weekly"),     s_weekly,  0},
+  {NT_("monthly"),    s_monthly, 0},
+  {NT_("hourly"),     s_hourly,  0},
+
+  {NT_("1st"),        s_wom,     0},
+  {NT_("2nd"),        s_wom,     1},
+  {NT_("3rd"),        s_wom,     2},
+  {NT_("4th"),        s_wom,     3},
+  {NT_("5th"),        s_wom,     4},
+  {NT_("6th"),        s_wom,     5},
+
+  {NT_("first"),      s_wom,     0},
+  {NT_("second"),     s_wom,     1},
+  {NT_("third"),      s_wom,     2},
+  {NT_("fourth"),     s_wom,     3},
+  {NT_("fifth"),      s_wom,     4},
+  {NT_("sixth"),      s_wom,     5},
+  {NULL,         s_none,    0}
 };
 
 static bool have_hour, have_mday, have_wday, have_month, have_wom;
@@ -132,34 +140,42 @@ static void set_defaults()
    set_bits(0, 30, lrun.mday);
    set_bits(0, 6,  lrun.wday);
    set_bits(0, 11, lrun.month);
-   set_bits(0, 4,  lrun.wom);
+   set_bits(0, 5,  lrun.wom);
    set_bits(0, 53, lrun.woy);
 }
 
 
-/* Keywords (RHS) permitted in Run records */
-static struct s_kw RunFields[] = {
-   {"pool",             'P'},
-   {"fullpool",         'f'},
-   {"incrementalpool",  'i'},
-   {"differentialpool", 'd'},
-   {"level",            'L'},
-   {"storage",          'S'},
-   {"messages",         'M'},
-   {"priority",         'p'},
-   {"spooldata",        's'},
-   {NULL,                0}
+/*
+ * Keywords (RHS) permitted in Run records
+ *
+ *    name              token
+ */
+s_kw RunFields[] = {
+   {"Pool",              'P'},
+   {"FullPool",          'f'},
+   {"IncrementalPool",   'i'},
+   {"DifferentialPool",  'd'},
+   {"Level",             'L'},
+   {"Storage",           'S'},
+   {"Messages",          'M'},
+   {"Priority",          'p'},
+   {"SpoolData",         's'},
+   {"writepartafterjob", 'W'},
+   {"MaxRunSchedTime",   'm'},
+   {"Accurate",          'a'},
+   {"NextPool",          'N'},
+   {NULL,                 0}
 };
 
-/* 
- * Store Schedule Run information   
- * 
+/*
+ * Store Schedule Run information
+ *
  * Parse Run statement:
  *
  *  Run <keyword=value ...> [on] 2 january at 23:45
  *
  *   Default Run time is daily at 0:0
- *  
+ *
  *   There can be multiple run statements, they are simply chained
  *   together.
  *
@@ -168,10 +184,10 @@ void store_run(LEX *lc, RES_ITEM *item, int index, int pass)
 {
    int i, j;
    bool found;
+   utime_t utime;
    int token, state, state2 = 0, code = 0, code2 = 0;
    int options = lc->options;
-   RUN **run = (RUN **)(item->value);  
-   RUN *trun;
+   RUN **run = (RUN **)(item->value);
    char *p;
    RES *res;
 
@@ -185,117 +201,162 @@ void store_run(LEX *lc, RES_ITEM *item, int index, int pass)
    for (found=true; found; ) {
       found = false;
       token = lex_get_token(lc, T_NAME);
-      for (i=0; RunFields[i].name; i++) {
-        if (strcasecmp(lc->str, RunFields[i].name) == 0) {
-           found = true;
-           if (lex_get_token(lc, T_ALL) != T_EQUALS) {
-               scan_err1(lc, "Expected an equals, got: %s", lc->str);
-              /* NOT REACHED */ 
-           }
-           switch (RunFields[i].token) {
+      for (i=0; !found && RunFields[i].name; i++) {
+         if (strcasecmp(lc->str, RunFields[i].name) == 0) {
+            found = true;
+            if (lex_get_token(lc, T_ALL) != T_EQUALS) {
+               scan_err1(lc, _("Expected an equals, got: %s"), lc->str);
+               /* NOT REACHED */
+            }
+            switch (RunFields[i].token) {
             case 's':                 /* Data spooling */
-              token = lex_get_token(lc, T_NAME);
-               if (strcasecmp(lc->str, "yes") == 0) {
-                 lrun.spool_data = true;
-                 lrun.spool_data_set = true;
-               } else if (strcasecmp(lc->str, "no") == 0) {
-                 lrun.spool_data = false;
-                 lrun.spool_data_set = true;
-              } else {
+               token = lex_get_token(lc, T_NAME);
+               if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
+                  lrun.spool_data = true;
+                  lrun.spool_data_set = true;
+               } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
+                  lrun.spool_data = false;
+                  lrun.spool_data_set = true;
+               } else {
+                  scan_err1(lc, _("Expect a YES or NO, got: %s"), lc->str);
+               }
+               break;
+            case 'W':                 /* Write part after job */
+               token = lex_get_token(lc, T_NAME);
+               if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
+                  lrun.write_part_after_job = true;
+                  lrun.write_part_after_job_set = true;
+               } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
+                  lrun.write_part_after_job = false;
+                  lrun.write_part_after_job_set = true;
+               } else {
                   scan_err1(lc, _("Expect a YES or NO, got: %s"), lc->str);
-              }
-              break;
+               }
+               break;
             case 'L':                 /* level */
-              token = lex_get_token(lc, T_NAME);
-              for (j=0; joblevels[j].level_name; j++) {
-                 if (strcasecmp(lc->str, joblevels[j].level_name) == 0) {
-                    lrun.level = joblevels[j].level;
-                    lrun.job_type = joblevels[j].job_type;
-                    j = 0;
-                    break;
-                 }
-              }
-              if (j != 0) {
+               token = lex_get_token(lc, T_NAME);
+               for (j=0; joblevels[j].level_name; j++) {
+                  if (strcasecmp(lc->str, joblevels[j].level_name) == 0) {
+                     lrun.level = joblevels[j].level;
+                     lrun.job_type = joblevels[j].job_type;
+                     lrun.level_set = true;
+                     j = 0;
+                     break;
+                  }
+               }
+               if (j != 0) {
                   scan_err1(lc, _("Job level field: %s not found in run record"), lc->str);
-                 /* NOT REACHED */
-              }
-              break;
+                  /* NOT REACHED */
+               }
+               break;
             case 'p':                 /* Priority */
-              token = lex_get_token(lc, T_PINT32);
-              if (pass == 2) {
-                 lrun.Priority = lc->pint32_val;
-              }
-              break;
+               token = lex_get_token(lc, T_PINT32);
+               if (pass == 2) {
+                  lrun.Priority = lc->pint32_val;
+                  lrun.priority_set = true;
+               }
+               break;
             case 'P':                 /* Pool */
+            case 'N':                 /* NextPool */
             case 'f':                 /* FullPool */
+            case 'v':                 /* VFullPool */
             case 'i':                 /* IncPool */
             case 'd':                 /* DifPool */
-              token = lex_get_token(lc, T_NAME);
-              if (pass == 2) {
-                 res = GetResWithName(R_POOL, lc->str);
-                 if (res == NULL) {
-                     scan_err1(lc, "Could not find specified Pool Resource: %s",
-                               lc->str);
-                    /* NOT REACHED */
-                 }
-                 switch(RunFields[i].token) {
+               token = lex_get_token(lc, T_NAME);
+               if (pass == 2) {
+                  res = GetResWithName(R_POOL, lc->str);
+                  if (res == NULL) {
+                     scan_err1(lc, _("Could not find specified Pool Resource: %s"),
+                                lc->str);
+                     /* NOT REACHED */
+                  }
+                  switch(RunFields[i].token) {
                   case 'P':
-                    lrun.pool = (POOL *)res;
-                    break;
+                     lrun.pool = (POOL *)res;
+                     break;
+                  case 'N':
+                     lrun.next_pool = (POOL *)res;
+                     break;
                   case 'f':
-                    lrun.full_pool = (POOL *)res;
-                    break;
+                     lrun.full_pool = (POOL *)res;
+                     break;
+                  case 'v':
+                     lrun.vfull_pool = (POOL *)res;
+                     break;
                   case 'i':
-                    lrun.inc_pool = (POOL *)res;
-                    break;
+                     lrun.inc_pool = (POOL *)res;
+                     break;
                   case 'd':
-                    lrun.dif_pool = (POOL *)res;
-                    break;
-                 }
-              }
-              break;
+                     lrun.diff_pool = (POOL *)res;
+                     break;
+                  }
+               }
+               break;
             case 'S':                 /* storage */
-              token = lex_get_token(lc, T_NAME);
-              if (pass == 2) {
-                 res = GetResWithName(R_STORAGE, lc->str);
-                 if (res == NULL) {
-                     scan_err1(lc, "Could not find specified Storage Resource: %s",
-                               lc->str);
-                    /* NOT REACHED */
-                 }
-                 lrun.storage = (STORE *)res;
-              }
-              break;
+               token = lex_get_token(lc, T_NAME);
+               if (pass == 2) {
+                  res = GetResWithName(R_STORAGE, lc->str);
+                  if (res == NULL) {
+                     scan_err1(lc, _("Could not find specified Storage Resource: %s"),
+                                lc->str);
+                     /* NOT REACHED */
+                  }
+                  lrun.storage = (STORE *)res;
+               }
+               break;
             case 'M':                 /* messages */
-              token = lex_get_token(lc, T_NAME);
-              if (pass == 2) {
-                 res = GetResWithName(R_MSGS, lc->str);
-                 if (res == NULL) {
-                     scan_err1(lc, "Could not find specified Messages Resource: %s",
-                               lc->str);
-                    /* NOT REACHED */
-                 }
-                 lrun.msgs = (MSGS *)res;
-              }
-              break;
-           default:
-               scan_err1(lc, "Expected a keyword name, got: %s", lc->str);
-              /* NOT REACHED */
-              break;
-           } /* end switch */     
-        } /* end if strcasecmp */
+               token = lex_get_token(lc, T_NAME);
+               if (pass == 2) {
+                  res = GetResWithName(R_MSGS, lc->str);
+                  if (res == NULL) {
+                     scan_err1(lc, _("Could not find specified Messages Resource: %s"),
+                                lc->str);
+                     /* NOT REACHED */
+                  }
+                  lrun.msgs = (MSGS *)res;
+               }
+               break;
+            case 'm':           /* max run sched time */
+               token = lex_get_token(lc, T_QUOTED_STRING);
+               if (!duration_to_utime(lc->str, &utime)) {
+                  scan_err1(lc, _("expected a time period, got: %s"), lc->str);
+                  return;
+               }
+               lrun.MaxRunSchedTime = utime;
+               lrun.MaxRunSchedTime_set = true;
+               break;
+            case 'a':           /* accurate */
+               token = lex_get_token(lc, T_NAME);
+               if (strcasecmp(lc->str, "yes") == 0 || strcasecmp(lc->str, "true") == 0) {
+                  lrun.accurate = true;
+                  lrun.accurate_set = true;
+               } else if (strcasecmp(lc->str, "no") == 0 || strcasecmp(lc->str, "false") == 0) {
+                  lrun.accurate = false;
+                  lrun.accurate_set = true;
+               } else {
+                  scan_err1(lc, _("Expect a YES or NO, got: %s"), lc->str);
+               }
+               break;
+            default:
+               scan_err1(lc, _("Expected a keyword name, got: %s"), lc->str);
+               /* NOT REACHED */
+               break;
+            } /* end switch */
+         } /* end if strcasecmp */
       } /* end for RunFields */
 
       /* At this point, it is not a keyword. Check for old syle
        * Job Levels without keyword. This form is depreciated!!!
        */
-      for (j=0; joblevels[j].level_name; j++) {
-        if (strcasecmp(lc->str, joblevels[j].level_name) == 0) {
-           lrun.level = joblevels[j].level;
-           lrun.job_type = joblevels[j].job_type;
-           found = true;
-           break;
-        }
+      if (!found) {
+         for (j=0; joblevels[j].level_name; j++) {
+            if (strcasecmp(lc->str, joblevels[j].level_name) == 0) {
+               lrun.level = joblevels[j].level;
+               lrun.job_type = joblevels[j].job_type;
+               found = true;
+               break;
+            }
+         }
       }
    } /* end for found */
 
@@ -308,289 +369,320 @@ void store_run(LEX *lc, RES_ITEM *item, int index, int pass)
    set_defaults();
 
    for ( ; token != T_EOL; (token = lex_get_token(lc, T_ALL))) {
-      int len, pm = 0;
+      int len;
+      bool pm = false;
+      bool am = false;
       switch (token) {
       case T_NUMBER:
-        state = s_mday;
-        code = atoi(lc->str) - 1;
-        if (code < 0 || code > 30) {
+         state = s_mday;
+         code = atoi(lc->str) - 1;
+         if (code < 0 || code > 30) {
             scan_err0(lc, _("Day number out of range (1-31)"));
-        }
-        break;
-      case T_NAME:                /* this handles drop through from keyword */
+         }
+         break;
+      case T_NAME:                 /* this handles drop through from keyword */
       case T_UNQUOTED_STRING:
          if (strchr(lc->str, (int)'-')) {
-           state = s_range;
-           break;
-        }
+            state = s_range;
+            break;
+         }
          if (strchr(lc->str, (int)':')) {
-           state = s_time;
-           break;
-        }
+            state = s_time;
+            break;
+         }
          if (lc->str_len == 3 && (lc->str[0] == 'w' || lc->str[0] == 'W') &&
-            is_an_integer(lc->str+1)) {
-           code = atoi(lc->str+1);
-           if (code < 0 || code > 53) {
+             is_an_integer(lc->str+1)) {
+            code = atoi(lc->str+1);
+            if (code < 0 || code > 53) {
                scan_err0(lc, _("Week number out of range (0-53)"));
-           }
-           state = s_woy;            /* week of year */
-           break;
-        }
-        /* everything else must be a keyword */
-        for (i=0; keyw[i].name; i++) {
-           if (strcasecmp(lc->str, keyw[i].name) == 0) {
-              state = keyw[i].state;
-              code   = keyw[i].code;
-              i = 0;
-              break;
-           }
-        }
-        if (i != 0) {
+              /* NOT REACHED */
+            }
+            state = s_woy;            /* week of year */
+            break;
+         }
+         /* everything else must be a keyword */
+         for (i=0; keyw[i].name; i++) {
+            if (strcasecmp(lc->str, keyw[i].name) == 0) {
+               state = keyw[i].state;
+               code   = keyw[i].code;
+               i = 0;
+               break;
+            }
+         }
+         if (i != 0) {
             scan_err1(lc, _("Job type field: %s in run record not found"), lc->str);
-           /* NOT REACHED */
-        }
-        break;
+            /* NOT REACHED */
+         }
+         break;
       case T_COMMA:
-        continue;
+         continue;
       default:
          scan_err2(lc, _("Unexpected token: %d:%s"), token, lc->str);
-        /* NOT REACHED */
-        break;
+         /* NOT REACHED */
+         break;
       }
       switch (state) {
       case s_none:
-        continue;
-      case s_mday:                /* day of month */
-        if (!have_mday) {
-           clear_bits(0, 30, lrun.mday);
-           have_mday = true;
-        }
-        set_bit(code, lrun.mday);
-        break;
-      case s_month:               /* month of year */
-        if (!have_month) {
-           clear_bits(0, 11, lrun.month);
-           have_month = true;
-        }
-        set_bit(code, lrun.month);
-        break;
-      case s_wday:                /* week day */
-        if (!have_wday) {
-           clear_bits(0, 6, lrun.wday);
-           have_wday = true;
-        }
-        set_bit(code, lrun.wday);
-        break;
-      case s_wom:                 /* Week of month 1st, ... */
-        if (!have_wom) {
-           clear_bits(0, 4, lrun.wom);
-           have_wom = true;
-        }
-        set_bit(code, lrun.wom);
-        break;
+         continue;
+      case s_mday:                 /* day of month */
+         if (!have_mday) {
+            clear_bits(0, 30, lrun.mday);
+            have_mday = true;
+         }
+         set_bit(code, lrun.mday);
+         break;
+      case s_month:                /* month of year */
+         if (!have_month) {
+            clear_bits(0, 11, lrun.month);
+            have_month = true;
+         }
+         set_bit(code, lrun.month);
+         break;
+      case s_wday:                 /* week day */
+         if (!have_wday) {
+            clear_bits(0, 6, lrun.wday);
+            have_wday = true;
+         }
+         set_bit(code, lrun.wday);
+         break;
+      case s_wom:                  /* Week of month 1st, ... */
+         if (!have_wom) {
+            clear_bits(0, 5, lrun.wom);
+            have_wom = true;
+         }
+         set_bit(code, lrun.wom);
+         break;
       case s_woy:
-        if (!have_woy) {
-           clear_bits(0, 53, lrun.woy);
-           have_woy = true;
-        }
-        set_bit(code, lrun.woy);
-        break;
-      case s_time:                /* time */
-        if (!have_at) {
+         if (!have_woy) {
+            clear_bits(0, 53, lrun.woy);
+            have_woy = true;
+         }
+         set_bit(code, lrun.woy);
+         break;
+      case s_time:                 /* time */
+         if (!have_at) {
             scan_err0(lc, _("Time must be preceded by keyword AT."));
-           /* NOT REACHED */
-        }
-        if (!have_hour) {
-           clear_bits(0, 23, lrun.hour);
-        }
+            /* NOT REACHED */
+         }
+         if (!have_hour) {
+            clear_bits(0, 23, lrun.hour);
+         }
+//       Dmsg1(000, "s_time=%s\n", lc->str);
          p = strchr(lc->str, ':');
-        if (!p)  {
+         if (!p)  {
             scan_err0(lc, _("Time logic error.\n"));
-           /* NOT REACHED */
-        }
-        *p++ = 0;                 /* separate two halves */
-        code = atoi(lc->str);
-        len = strlen(p);
-         if (len > 2 && p[len-1] == 'm') {
-            if (p[len-2] == 'a') {
-              pm = 0;
-            } else if (p[len-2] == 'p') {
-              pm = 1;
-           } else {
-               scan_err0(lc, _("Bad time specification."));
-              /* NOT REACHED */
-           }
-        } else {
-           pm = 0;
-        }
-        code2 = atoi(p);
-        if (pm) {
-           code += 12;
-        }
-        if (code < 0 || code > 23 || code2 < 0 || code2 > 59) {
+            /* NOT REACHED */
+         }
+         *p++ = 0;                 /* separate two halves */
+         code = atoi(lc->str);     /* pick up hour */
+         code2 = atoi(p);          /* pick up minutes */
+         len = strlen(p);
+         if (len >= 2) {
+            p += 2;
+         }
+         if (strcasecmp(p, "pm") == 0) {
+            pm = true;
+         } else if (strcasecmp(p, "am") == 0) {
+            am = true;
+         } else if (len != 2) {
+            scan_err0(lc, _("Bad time specification."));
+            /* NOT REACHED */
+         }
+         /*
+          * Note, according to NIST, 12am and 12pm are ambiguous and
+          *  can be defined to anything.  However, 12:01am is the same
+          *  as 00:01 and 12:01pm is the same as 12:01, so we define
+          *  12am as 00:00 and 12pm as 12:00.
+          */
+         if (pm) {
+            /* Convert to 24 hour time */
+            if (code != 12) {
+               code += 12;
+            }
+         /* am */
+         } else if (am && code == 12) {
+            code -= 12;
+         }
+         if (code < 0 || code > 23 || code2 < 0 || code2 > 59) {
             scan_err0(lc, _("Bad time specification."));
-           /* NOT REACHED */
-        }
-        /****FIXME**** convert to UTC */
-        set_bit(code, lrun.hour);
-        lrun.minute = code2;
-        have_hour = true;
-        break;
+            /* NOT REACHED */
+         }
+//       Dmsg2(000, "hour=%d min=%d\n", code, code2);
+         set_bit(code, lrun.hour);
+         lrun.minute = code2;
+         have_hour = true;
+         break;
       case s_at:
-        have_at = true;
-        break;
+         have_at = true;
+         break;
+      case s_ldom:
+         if (!have_mday) {
+            clear_bits(0, 30, lrun.mday);
+            have_mday = true;
+         }
+         lrun.last_day_set = true;
+         set_bit(31, lrun.mday);   /* day 32 => last day of month */
+         break;
       case s_range:
          p = strchr(lc->str, '-');
-        if (!p) {
+         if (!p) {
             scan_err0(lc, _("Range logic error.\n"));
-        }
-        *p++ = 0;                 /* separate two halves */
-
-        /* Check for day range */
-        if (is_an_integer(lc->str) && is_an_integer(p)) {
-           code = atoi(lc->str) - 1;
-           code2 = atoi(p) - 1;
-           if (code < 0 || code > 30 || code2 < 0 || code2 > 30) {
+         }
+         *p++ = 0;                 /* separate two halves */
+
+         /* Check for day range */
+         if (is_an_integer(lc->str) && is_an_integer(p)) {
+            code = atoi(lc->str) - 1;
+            code2 = atoi(p) - 1;
+            if (code < 0 || code > 30 || code2 < 0 || code2 > 30) {
                scan_err0(lc, _("Bad day range specification."));
-           }
-           if (!have_mday) {
-              clear_bits(0, 30, lrun.mday);
-              have_mday = true;
-           }
-           if (code < code2) {
-              set_bits(code, code2, lrun.mday);
-           } else {
-              set_bits(code, 30, lrun.mday);
-              set_bits(0, code2, lrun.mday);
-           }
-           break;
-        }
-        /* Check for week of year range */
-        if (strlen(lc->str) == 3 && strlen(p) == 3 &&
+            }
+            if (!have_mday) {
+               clear_bits(0, 30, lrun.mday);
+               have_mday = true;
+            }
+            if (code < code2) {
+               set_bits(code, code2, lrun.mday);
+            } else {
+               set_bits(code, 30, lrun.mday);
+               set_bits(0, code2, lrun.mday);
+            }
+            break;
+         }
+         /* Check for week of year range */
+         if (strlen(lc->str) == 3 && strlen(p) == 3 &&
              (lc->str[0] == 'w' || lc->str[0] == 'W') &&
              (p[0] == 'w' || p[0] == 'W') &&
-            is_an_integer(lc->str+1) && is_an_integer(p+1)) {
-           code = atoi(lc->str+1);
-           code2 = atoi(p+1);
-           if (code < 0 || code > 53 || code2 < 0 || code2 > 53) {
+             is_an_integer(lc->str+1) && is_an_integer(p+1)) {
+            code = atoi(lc->str+1);
+            code2 = atoi(p+1);
+            if (code < 0 || code > 53 || code2 < 0 || code2 > 53) {
                scan_err0(lc, _("Week number out of range (0-53)"));
-           }
-           if (!have_woy) {
-              clear_bits(0, 53, lrun.woy);
-              have_woy = true;
-           }
-           if (code < code2) {
-              set_bits(code, code2, lrun.woy);
-           } else {
-              set_bits(code, 53, lrun.woy);
-              set_bits(0, code2, lrun.woy);
-           }
-           break;
-        }
-        /* lookup first half of keyword range (week days or months) */
-        lcase(lc->str);
-        for (i=0; keyw[i].name; i++) {
-           if (strcmp(lc->str, keyw[i].name) == 0) {
-              state = keyw[i].state;
-              code   = keyw[i].code;
-              i = 0;
-              break;
-           }
-        }
-        if (i != 0 || (state != s_month && state != s_wday && state != s_wom)) {
+            }
+            if (!have_woy) {
+               clear_bits(0, 53, lrun.woy);
+               have_woy = true;
+            }
+            if (code < code2) {
+               set_bits(code, code2, lrun.woy);
+            } else {
+               set_bits(code, 53, lrun.woy);
+               set_bits(0, code2, lrun.woy);
+            }
+            break;
+         }
+         /* lookup first half of keyword range (week days or months) */
+         lcase(lc->str);
+         for (i=0; keyw[i].name; i++) {
+            if (strcasecmp(lc->str, keyw[i].name) == 0) {
+               state = keyw[i].state;
+               code   = keyw[i].code;
+               i = 0;
+               break;
+            }
+         }
+         if (i != 0 || (state != s_month && state != s_wday && state != s_wom)) {
             scan_err0(lc, _("Invalid month, week or position day range"));
-           /* NOT REACHED */
-        }
-
-        /* Lookup end of range */
-        lcase(p);
-        for (i=0; keyw[i].name; i++) {
-           if (strcmp(p, keyw[i].name) == 0) {
-              state2  = keyw[i].state;
-              code2   = keyw[i].code;
-              i = 0;
-              break;
-           }
-        }
-        if (i != 0 || state != state2 || code == code2) {
+            /* NOT REACHED */
+         }
+
+         /* Lookup end of range */
+         lcase(p);
+         for (i=0; keyw[i].name; i++) {
+            if (strcasecmp(p, keyw[i].name) == 0) {
+               state2  = keyw[i].state;
+               code2   = keyw[i].code;
+               i = 0;
+               break;
+            }
+         }
+         if (i != 0 || state != state2 || code == code2) {
             scan_err0(lc, _("Invalid month, weekday or position range"));
-           /* NOT REACHED */
-        }
-        if (state == s_wday) {
-           if (!have_wday) {
-              clear_bits(0, 6, lrun.wday);
-              have_wday = true;
-           }
-           if (code < code2) {
-              set_bits(code, code2, lrun.wday);
-           } else {
-              set_bits(code, 6, lrun.wday);
-              set_bits(0, code2, lrun.wday);
-           }
-        } else if (state == s_month) {
-           if (!have_month) {
-              clear_bits(0, 11, lrun.month);
-              have_month = true;
-           }
-           if (code < code2) {
-              set_bits(code, code2, lrun.month);
-           } else {
-              /* this is a bit odd, but we accept it anyway */
-              set_bits(code, 11, lrun.month);
-              set_bits(0, code2, lrun.month);
-           }
-        } else {
-           /* Must be position */
-           if (!have_wom) {
-              clear_bits(0, 4, lrun.wom);
-              have_wom = true;
-           }
-           if (code < code2) {
-              set_bits(code, code2, lrun.wom);
-           } else {
-              set_bits(code, 4, lrun.wom);
-              set_bits(0, code2, lrun.wom);
-           }
-        }                      
-        break;
+            /* NOT REACHED */
+         }
+         if (state == s_wday) {
+            if (!have_wday) {
+               clear_bits(0, 6, lrun.wday);
+               have_wday = true;
+            }
+            if (code < code2) {
+               set_bits(code, code2, lrun.wday);
+            } else {
+               set_bits(code, 6, lrun.wday);
+               set_bits(0, code2, lrun.wday);
+            }
+         } else if (state == s_month) {
+            if (!have_month) {
+               clear_bits(0, 11, lrun.month);
+               have_month = true;
+            }
+            if (code < code2) {
+               set_bits(code, code2, lrun.month);
+            } else {
+               /* this is a bit odd, but we accept it anyway */
+               set_bits(code, 11, lrun.month);
+               set_bits(0, code2, lrun.month);
+            }
+         } else {
+            /* Must be position */
+            if (!have_wom) {
+               clear_bits(0, 5, lrun.wom);
+               have_wom = true;
+            }
+            if (code < code2) {
+               set_bits(code, code2, lrun.wom);
+            } else {
+               set_bits(code, 5, lrun.wom);
+               set_bits(0, code2, lrun.wom);
+            }
+         }
+         break;
       case s_hourly:
-        have_hour = true;
-        set_bits(0, 23, lrun.hour);
-        break;
+         have_hour = true;
+         set_bits(0, 23, lrun.hour);
+         break;
       case s_weekly:
-        have_mday = have_wom = have_woy = true;
-        set_bits(0, 30, lrun.mday);
-        set_bits(0, 4,  lrun.wom);
-        set_bits(0, 53, lrun.woy);
-        break;
+         have_mday = have_wom = have_woy = true;
+         set_bits(0, 30, lrun.mday);
+         set_bits(0, 5,  lrun.wom);
+         set_bits(0, 53, lrun.woy);
+         break;
       case s_daily:
-        have_mday = true;
-        set_bits(0, 6, lrun.wday);
-        break;
+         have_mday = true;
+         set_bits(0, 6, lrun.wday);
+         break;
       case s_monthly:
-        have_month = true;
-        set_bits(0, 11, lrun.month);
-        break;
+         have_month = true;
+         set_bits(0, 11, lrun.month);
+         break;
       default:
          scan_err0(lc, _("Unexpected run state\n"));
-        /* NOT REACHED */
-        break;
+         /* NOT REACHED */
+         break;
       }
    }
 
    /* Allocate run record, copy new stuff into it,
-    * and link it into the list of run records 
+    * and append it to the list of run records
     * in the schedule resource.
     */
    if (pass == 2) {
-      trun = (RUN *)malloc(sizeof(RUN));
-      memcpy(trun, &lrun, sizeof(RUN));
-      if (*run) {
-        trun->next = *run;
+      RUN *tail;
+
+      /* Create new run record */
+      RUN *nrun = (RUN *)malloc(sizeof(RUN));
+      memcpy(nrun, &lrun, sizeof(RUN));
+      nrun ->next = NULL;
+
+      if (!*run) {                    /* if empty list */
+         *run = nrun;                 /* add new record */
+      } else {
+         for (tail = *run; tail->next; tail=tail->next)
+            {  }
+         tail->next = nrun;
       }
-      *run = trun;
    }
 
-   lc->options = options;            /* restore scanner options */
+   lc->options = options;             /* restore scanner options */
    set_bit(index, res_all.res_sch.hdr.item_present);
 }