- Test multiple simultaneous Volumes
 - Document FInclude ...
 
+- Test and implement get_pint and get_yesno.
+- Implement timeout in response() when it should come quickly.
+
 - Figure out how to use ssh or stunnel to protect Bacula communications.
 
 After 1.30:
 
 #  $1 = path to executable
 #  $2 = main pid of running program to be traced back.
 #
-
-gdb -quiet -batch -x @sbindir@/btraceback.gdb $1 $2 2>&1 | mail -s "Bacula traceback" @dump_email@
+gdb -quiet -batch -x @sbindir@/btraceback.gdb $1 $2 2>&1 \
+ | @sbindir@/smtp -h @smtp_host@ -s "Bacula traceback" @dump_email@
 
 # Below is some old code that did the traceback from a core
 #  dump. However, for some odd reason, core dumps are not
 
        <widget>
          <class>GtkLabel</class>
          <name>label107</name>
-         <width>70</width>
+         <width>78</width>
          <label>Storage:</label>
          <justify>GTK_JUSTIFY_CENTER</justify>
          <wrap>False</wrap>
        <widget>
          <class>GtkLabel</class>
          <name>label109</name>
-         <width>70</width>
+         <width>78</width>
          <label>Pool:</label>
          <justify>GTK_JUSTIFY_LEFT</justify>
          <wrap>False</wrap>
        <widget>
          <class>GtkLabel</class>
          <name>label111</name>
-         <width>70</width>
+         <width>78</width>
          <label>Volume Name:</label>
          <justify>GTK_JUSTIFY_LEFT</justify>
          <wrap>False</wrap>
-         <xalign>0.18</xalign>
+         <xalign>0.17</xalign>
          <yalign>0.5</yalign>
          <xpad>0</xpad>
          <ypad>0</ypad>
        </widget>
       </widget>
 
+      <widget>
+       <class>GtkHBox</class>
+       <name>hbox48</name>
+       <border_width>5</border_width>
+       <homogeneous>False</homogeneous>
+       <spacing>0</spacing>
+       <child>
+         <padding>0</padding>
+         <expand>True</expand>
+         <fill>True</fill>
+       </child>
+
+       <widget>
+         <class>GtkLabel</class>
+         <name>slot1</name>
+         <width>93</width>
+         <label>Slot:</label>
+         <justify>GTK_JUSTIFY_LEFT</justify>
+         <wrap>False</wrap>
+         <xalign>0.09</xalign>
+         <yalign>0.5</yalign>
+         <xpad>0</xpad>
+         <ypad>0</ypad>
+         <child>
+           <padding>0</padding>
+           <expand>False</expand>
+           <fill>True</fill>
+         </child>
+       </widget>
+
+       <widget>
+         <class>GtkSpinButton</class>
+         <name>label_slot</name>
+         <can_focus>True</can_focus>
+         <climb_rate>1</climb_rate>
+         <digits>0</digits>
+         <numeric>True</numeric>
+         <update_policy>GTK_UPDATE_ALWAYS</update_policy>
+         <snap>False</snap>
+         <wrap>False</wrap>
+         <value>0</value>
+         <lower>0</lower>
+         <upper>10000</upper>
+         <step>1</step>
+         <page>10</page>
+         <page_size>10</page_size>
+         <child>
+           <padding>0</padding>
+           <expand>True</expand>
+           <fill>True</fill>
+         </child>
+       </widget>
+
+       <widget>
+         <class>Placeholder</class>
+       </widget>
+      </widget>
+
       <widget>
        <class>GtkLabel</class>
        <name>label113</name>
 
       V(mutex);
       /* Try again */
    }
+   jcr->acquired_resource_locks = 1;
 #endif
    return 1;
 }
  */
 static void release_resource_locks(JCR *jcr)
 {
+   if (!jcr->acquired_resource_locks) {
+      return;                        /* Job canceled, no locks acquired */
+   }
 #ifdef USE_SEMAPHORE
    P(mutex);
    sem_unlock(&jcr->store->sem);
 
 
 /* ua_input.c */
 int get_cmd(UAContext *ua, char *prompt);
+int get_pint(UAContext *ua, char *prompt);
+int get_yesno(UAContext *ua, char *prompt);
 void parse_ua_args(UAContext *ua);
 
 /* ua_output.c */
 
 int find_arg_keyword(UAContext *ua, char **list);
 int find_arg(UAContext *ua, char *keyword);
+int find_arg_with_value(UAContext *ua, char *keyword);
 int do_keyword_prompt(UAContext *ua, char *msg, char **list);
 int confirm_retention(UAContext *ua, utime_t *ret, char *msg);
 
 
    int automount;                     /* if set, mount after label */
    int quit;                          /* if set, quit */
    int verbose;                       /* set for normal UA verbosity */
+   uint32_t pint32_val;               /* positive integer */
+   int32_t  int32_val;                /* positive/negative */
 } UAContext;
 
 #endif
 
    }
 getVolName:
    if (num == 0) {
-      if (!get_cmd(ua, _("Enter Volume name: ")) || ua->cmd[0] == '.') {
+      if (!get_cmd(ua, _("Enter Volume name: "))) {
         return 1;
       }
    } else {
-      if (!get_cmd(ua, _("Enter base volume name: ")) || ua->cmd[0] == '.') {
+      if (!get_cmd(ua, _("Enter base volume name: "))) {
         return 1;
       }
    }
    }
 
    if (store && store->autochanger) {
-      if (!get_cmd(ua, _("Enter slot (0 for none): ")) || ua->cmd[0] == '.') {
+      if (!get_cmd(ua, _("Enter slot (0 for none): "))) {
         return 1;
       }
       slot = atoi(ua->cmd);
         return 1;
       }
       if (njobs == 1) {
-         if (!get_cmd(ua, _("Confirm cancel (yes/no): "))) {
-           return 1;
-        }
-         if (strcasecmp(ua->cmd, _("yes")) != 0) {
+         if (!get_yesno(ua, _("Confirm cancel (yes/no): ")) || ua->pint32_val == 0) {
            return 1;
         }
       }
    Dmsg1(120, "setdebug:%s:\n", cmd);
 
    level = -1;
-   for (i=1; i<ua->argc; i++) {
-      if (strcasecmp(ua->argk[i], _("level")) == 0 && ua->argv[i]) {
-        level = atoi(ua->argv[i]);
-        break;
-      }
+   i = find_arg_with_value(ua, _("level"));
+   if (i >= 0) {
+      level = atoi(ua->argv[i]);
    }
    if (level < 0) {
       if (!get_cmd(ua, _("Enter new debug level: "))) {
 
    JCR *jcr = NULL;
    int a;
    
+   bsendmsg(ua, "The Director will segment fault.\n");
    a = jcr->JobId; /* ref NULL pointer */
+   jcr->JobId = 1000; /* another ref NULL pointer */
    return 0;
 }
 
 
       if (strcmp(ua->cmd, ".messages") == 0) {
         qmessagescmd(ua, ua->cmd);
       }
-      /* ****FIXME**** if .command, go off and do it. For now ignore it. */
-      if (ua->cmd[0] == '.' && ua->cmd[1] != 0) {
-        continue;                    /* dot command */
+      /* Lone dot => break */
+      if (ua->cmd[0] == '.' && ua->cmd[1] == 0) {
+        return 0;
       }
-      /* Lone dot => break or actual response */
       break;
    }
    return 1;
 }
 
+/* 
+ * Get a positive integer
+ *  Returns:  0 if failure
+ *           1 if success => value in ua->pint32_val
+ */
+int get_pint(UAContext *ua, char *prompt)
+{
+   double dval;
+   ua->pint32_val = 0;
+   for (;;) {
+      if (!get_cmd(ua, prompt)) {
+        return 0;
+      }
+      if (!is_a_number(ua->cmd)) {
+         bsendmsg(ua, "Expected a positive integer, got: %s\n", ua->cmd);
+        continue;
+      }
+      errno = 0;
+      dval = strtod(ua->cmd, NULL);
+      if (errno != 0 || dval < 0) {
+         bsendmsg(ua, "Expected a positive integer, got: %s\n", ua->cmd);
+        continue;
+      }
+      ua->pint32_val = (uint32_t)dval;
+      return 1;
+   }
+}
+
+/* 
+ * Gets a yes or no response
+ *  Returns:  0 if failure
+ *           1 if success => ua->pint32_val == 1 for yes
+ *                           ua->pint32_val == 0 for no
+ */
+int get_yesno(UAContext *ua, char *prompt)
+{
+   int len;
+
+   ua->pint32_val = 0;
+   for (;;) {
+      if (!get_cmd(ua, prompt)) {
+        return 0;
+      }
+      len = strlen(ua->cmd);
+      if (len < 1 || len > 3) {
+        continue;
+      }
+      if (strncasecmp(ua->cmd, _("yes"), len) == 0) {
+        ua->pint32_val = 1;
+        return 1;
+      }
+      if (strncasecmp(ua->cmd, _("no"), len) == 0) {
+        return 1;
+      }
+      bsendmsg(ua, _("Invalid response. You must answer yes or no.\n"));
+   }
+}
+  
+
 void parse_ua_args(UAContext *ua)
 {
    return parse_command_args(ua->cmd, ua->args, &ua->argc, ua->argk, ua->argv);
 
 
    /* If autochanger, request slot */
    if (store->autochanger) {
+      int first = 1;
       for ( ;; ) {
-         if (!get_cmd(ua, _("Enter slot (0 for none): ")) || ua->cmd[0] == '.') {
-           return 1;
+        if (first) {
+            i = find_arg(ua, "slot"); 
+           if (i >= 0 && ua->argv[i]) {
+              mr.Slot = atoi(ua->argv[i]);
+           }
+           first = 0;
+        } else {
+            if (!get_cmd(ua, _("Enter slot (0 for none): ")) || ua->cmd[0] == '.') {
+              return 1;
+           }
+           mr.Slot = atoi(ua->cmd);
         }
-        mr.Slot = atoi(ua->cmd);
         if (mr.Slot >= 0) {          /* OK */
            break;
         }
          bsendmsg(ua, _("Slot numbers must be positive.\n"));
       }
    }
+
    bstrncpy(mr.MediaType, store->media_type, sizeof(mr.MediaType));
 
    /* Must select Pool if not already done */
 
    return -1;
 }
 
+int find_arg_with_value(UAContext *ua, char *keyword)
+{
+   for (int i=1; i<ua->argc; i++) {
+      if (strcasecmp(keyword, ua->argk[i]) == 0) {
+        if (ua->argv[i]) {
+           return i;
+        } else {
+           return -1;
+        }
+      }
+   }
+   return -1;
+}
+
+
 
 /* 
  * Given a list of keywords, prompt the user 
 
    memset(mr, 0, sizeof(MEDIA_DBR));
 
-   i = find_arg(ua, "volume");
-   if (i >= 0 && ua->argv[i]) {
+   i = find_arg_with_value(ua, "volume");
+   if (i >= 0) {
       bstrncpy(mr->VolumeName, ua->argv[i], sizeof(mr->VolumeName));
    }
    if (mr->VolumeName[0] == 0) {
    POOL *pool = NULL;
    int i;
    
-   for (i=1; i<ua->argc; i++) {
-      if (strcasecmp(ua->argk[i], _("pool")) == 0 && ua->argv[i]) {
-        pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
-        if (pool) {
-           return pool;
-        }
-         bsendmsg(ua, _("Error: Pool resource %s does not exist.\n"), ua->argv[i]);
-        break;
+   i = find_arg_with_value(ua, "pool");
+   if (i >= 0) {
+      pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
+      if (pool) {
+        return pool;
       }
+      bsendmsg(ua, _("Error: Pool resource %s does not exist.\n"), ua->argv[i]);
    }
    return select_pool_resource(ua);
 }
          sprintf(pmsg, "%s (1-%d): ", msg, ua->num_prompts-1);
       }
       /* Either a . or an @ will get you out of the loop */
-      if (!get_cmd(ua, pmsg) || *ua->cmd == '.' || *ua->cmd == '@') {
+      if (!get_cmd(ua, pmsg) || *ua->cmd == '@') {
         item = -1;                   /* error */
          bsendmsg(ua, _("Selection aborted, nothing done.\n"));
         break;
    STORE *store;
    int i;
 
-   i = find_arg(ua, "mediatype");
-   if (i >= 0 && ua->argv[i]) {
+   i = find_arg_with_value(ua, "mediatype");
+   if (i >= 0) {
       bstrncpy(MediaType, ua->argv[i], max_media);
       return 1;
    }
 
     *  a heartbeat, we simply send it on to the Director to
     *  keep him alive.
     */
-   for ( ;; ) {
-      n = bnet_recv(sd);
-      if (is_bnet_stop(sd)) {
-        break;
+   for ( ; !is_bnet_stop(sd); ) {
+      n = bnet_wait_data_intr(sd, 60);
+      if (n != 1) {
+        continue;
       }
+      n = bnet_recv(sd);
       if (n == BNET_SIGNAL && sd->msglen == BNET_HEARTBEAT) {
         bnet_sig(dir, BNET_HEARTBEAT);
       }
    }
    bnet_close(sd);
    bnet_close(dir);
+   jcr->duped_sd = NULL;
    return NULL;
 }
 
       bmicrosleep(0, 500);           /* avoid race */
    }
    jcr->duped_sd->timed_out = 1;      /* set timed_out to terminate read */
+   jcr->duped_sd->terminated = 1;     /* set to terminate read */
 
-   pthread_kill(hbtid, TIMEOUT_SIGNAL);  /* make heartbeat thread go away */
+   while (jcr->duped_sd) {
+      pthread_kill(hbtid, TIMEOUT_SIGNAL);  /* make heartbeat thread go away */
+      bmicrosleep(0, 20);
+   }
    pthread_join(hbtid, NULL);        /* wait for him to clean up */
 }
 
 
    char *RestoreWhere;                /* Where to restore the files */
    POOLMEM *client_uname;             /* client uname */ 
    int replace;                       /* Replace option */
+   int acquired_resource_locks;       /* set if resource locks acquired */
 #endif /* DIRECTOR_DAEMON */
 
 
 
  *          -1 if error
  */
 int 
-bnet_wait_data(BSOCK *bsock, int sec)
+bnet_wait_data(BSOCK *bsock, int sec)         
 {
    fd_set fdset;
    struct timeval tv;
    }
 }
 
+/*
+ * As above, but returns on interrupt
+ */
+int
+bnet_wait_data_intr(BSOCK *bsock, int sec)         
+{
+   fd_set fdset;
+   struct timeval tv;
+
+   FD_ZERO(&fdset);
+   FD_SET(bsock->fd, &fdset);
+   tv.tv_sec = sec;
+   tv.tv_usec = 0;
+   for ( ;; ) {
+      switch(select(bsock->fd + 1, &fdset, NULL, NULL, &tv)) {
+        case 0:                         /* timeout */
+           bsock->b_errno = 0;
+           return 0;
+        case -1:
+           bsock->b_errno = errno;
+           return -1;                  /* error return */
+        default:
+           bsock->b_errno = 0;
+           return 1;
+      }
+   }
+}
+
+
 static pthread_mutex_t ip_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 /*
 
 BSOCK *    bnet_connect           (void *jcr, int retry_interval,
               int max_retry_time, char *name, char *host, char *service, 
               int port, int verbose);
-int       bnet_wait_data         (BSOCK *bsock, int sec);
 void      bnet_close            (BSOCK *bsock);
 BSOCK *    init_bsock           (void *jcr, int sockfd, char *who, char *ip, int port);
 BSOCK *    dup_bsock            (BSOCK *bsock);
 char *    bnet_strerror         (BSOCK *bsock);
 char *    bnet_sig_to_ascii     (BSOCK *bsock);
 int       bnet_wait_data        (BSOCK *bsock, int sec);
+int       bnet_wait_data_intr   (BSOCK *bsock, int sec);
 int       bnet_despool          (BSOCK *bsock);
 int       is_bnet_stop          (BSOCK *bsock);
 int       is_bnet_error         (BSOCK *bsock);
 
       static char pid_buf[20];
       static char btpath[400];
       pid_t pid;
+      int exelen = strlen(exepath);
 
       fprintf(stderr, "Kaboom! %s, %s got signal %d. Attempting traceback.\n", 
              exename, my_name, sig);
 
-      if (strlen(exepath) + 12 > (int)sizeof(btpath)) {
+      if (exelen + 12 > (int)sizeof(btpath)) {
          strcpy(btpath, "btraceback");
       } else {
         strcpy(btpath, exepath);
-         strcat(btpath, "/btraceback");
+         if (btpath[exelen-1] != '/') {
+            strcat(btpath, "/btraceback");
+        } else {
+            strcat(btpath, "btraceback");
+        }
+      }
+      if (btpath[exelen-1] != '/') {
+         strcat(exepath, "/");
       }
-      strcat(exepath, "/");
       strcat(exepath, exename);
       if (chdir(working_directory) !=0) {  /* dump in working directory */
          Pmsg2(000, "chdir to %s failed. ERR=%s\n", working_directory,  strerror(errno));
         argv[1] = exepath;           /* path to exe */
         argv[2] = pid_buf;
         argv[3] = (char *)NULL;
+         fprintf(stderr, "Calling: %s %s %s\n", btpath, exepath, pid_buf);
         if (execv(btpath, argv) != 0) {
             printf("execv: %s failed: ERR=%s\n", btpath, strerror(errno));
         }
       default:                       /* parent */
         break;
       }
+      /* Parent continue here, waiting for child */
       sigdefault.sa_flags = 0;
       sigdefault.sa_handler = SIG_DFL;
       sigfillset(&sigdefault.sa_mask);
 
 #define TRACE_FILE 1  
 
 /* Turn this on if you want to try the new Job semaphore code */
-/* #define USE_SEMAPHORE */
+#define USE_SEMAPHORE
 
 /* IF you undefine this, Bacula will run 10X slower */
 #define NO_POLL_TEST 1