General:
 
 Changes to 1.37.41:
+13Oct05
+- Modify DVD code so that it keeps a state flag that indicates
+  when the freespace variable is valid. freespace_errno, now has
+  either 0 or an errno. There are no negative errnos.
+- Create is_freespace_ok() to test state flag. Also, set_freespace_ok()
+  and clear_freespace_ok(). Modify code to use them.  This
+  simplifies a bit the logic of the freespace code.
+- Edit 64bit debug values correctly in dvd.c
+- Fix %e variable to check num_parts rather than part.
+- Use static buffer instead of static buffers for FI_to_ascii()
+  and stream_to_ascii() debug routines in SD. This is to prevent
+  possible race conditions between two threads (possibly Phil's
+  unmount problem).
 12Oct05
 - Add a .dir command that separates arguments with commas. It
   is intended to be used in the tree routines to get a 
 
    DEV_RECORD rec;
    DCR *dcr = jcr->dcr;
    DEVICE *dev = dcr->dev;
+   char buf1[100], buf2[100];
 
 
    Dmsg0(100, "Start append data.\n");
          rec.data = ds->msg;            /* use message buffer */
 
          Dmsg4(850, "before writ_rec FI=%d SessId=%d Strm=%s len=%d\n",
-            rec.FileIndex, rec.VolSessionId, stream_to_ascii(rec.Stream,rec.FileIndex),
+            rec.FileIndex, rec.VolSessionId, 
+            stream_to_ascii(buf1, rec.Stream,rec.FileIndex),
             rec.data_len);
 
          while (!write_record_to_block(dcr->block, &rec)) {
          }
          jcr->JobBytes += rec.data_len;   /* increment bytes this job */
          Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
-            FI_to_ascii(rec.FileIndex), rec.VolSessionId,
-            stream_to_ascii(rec.Stream, rec.FileIndex), rec.data_len);
+            FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
+            stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
 
          /* Send attributes and MD5 to Director for Catalog */
          if (stream == STREAM_UNIX_ATTRIBUTES    || stream == STREAM_MD5_SIGNATURE ||
 
    int32_t  FileIndex;
    int32_t  Stream;
    int bhl, rhl;
+   char buf1[100], buf2[100];
 
    unser_begin(b->buf, BLKHDR1_LENGTH);
    unser_uint32(CheckSum);
       unser_int32(Stream);
       unser_uint32(data_len);
       Pmsg6(000, _("   Rec: VId=%u VT=%u FI=%s Strm=%s len=%d p=%x\n"),
-           VolSessionId, VolSessionTime, FI_to_ascii(FileIndex),
-           stream_to_ascii(Stream, FileIndex), data_len, p);
+           VolSessionId, VolSessionTime, FI_to_ascii(buf1, FileIndex),
+           stream_to_ascii(buf2, Stream, FileIndex), data_len, p);
       p += data_len + rhl;
   }
 }
       }
    }
    
-   if (dev->free_space_errno < 0) { /* Error while getting free space */
+   if (!dev->is_freespace_ok()) { /* Error while getting free space */
       char ed1[50], ed2[50];
       Dmsg1(10, "Cannot get free space on the device ERR=%s.\n", dev->errmsg);
       Jmsg(jcr, M_FATAL, 0, _("End of Volume \"%s\" at %u:%u on device %s "
            dev->file, dev->block_num, dev->print_name(),
            edit_uint64_with_commas(dev->part_size, ed1), edit_uint64_with_commas(dev->free_space, ed2),
            dev->free_space_errno, dev->errmsg);
-      dev->dev_errno = -dev->free_space_errno;
+      dev->dev_errno = dev->free_space_errno;
       return false;
    }
    
-   if ((dev->free_space_errno > 0 && (dev->part_size + block->binbuf) >= dev->free_space)) {
+   if ((dev->is_freespace_ok() && (dev->part_size + block->binbuf) >= dev->free_space)) {
       char ed1[50], ed2[50];
       Dmsg0(10, "==== Just enough free space on the device to write the current part...\n");
       Jmsg(jcr, M_INFO, 0, _("End of Volume \"%s\" at %u:%u on device %s "
 
 /* List just block information */
 static void do_blocks(char *infname)
 {
+   char buf1[100], buf2[100];
    for ( ;; ) {
       if (!read_block_from_device(dcr, NO_BLOCK_NUMBER_CHECK)) {
          Dmsg1(100, "!read_block(): ERR=%s\n", dev->strerror());
          Pmsg9(-1, _("File:blk=%u:%u blk_num=%u blen=%u First rec FI=%s SessId=%u SessTim=%u Strm=%s rlen=%d\n"),
               dev->file, dev->block_num,
               block->BlockNumber, block->block_len,
-              FI_to_ascii(rec->FileIndex), rec->VolSessionId, rec->VolSessionTime,
-              stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len);
+              FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, rec->VolSessionTime,
+              stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
          rec->remainder = 0;
       } else if (verbose > 1) {
          dump_block(block, "");
 
    uint64_t bytes;
    DEV_BLOCK *block = dcr->block;
    char ec1[50];
+   char buf1[100], buf2[100];
 
    blocks = block_size = tot_blocks = 0;
    bytes = 0;
          read_record_from_block(block, rec);
          Pmsg8(-1, _("Blk_block: %u dev_blk=%u blen=%u First rec FI=%s SessId=%u SessTim=%u Strm=%s rlen=%d\n"),
               block->BlockNumber, dev->block_num, block->block_len,
-              FI_to_ascii(rec->FileIndex), rec->VolSessionId, rec->VolSessionTime,
-              stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len);
+              FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId, rec->VolSessionTime,
+              stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
          rec->remainder = 0;
          free_record(rec);
       } else if (verbose > 1) {
    DEV_RECORD rec;
    DEV_BLOCK  *block = dcr->block;
    char ec1[50];
+   char buf1[100], buf2[100];
    int fd;
    uint32_t i;
    uint32_t min_block_size;
       }
 
       Dmsg4(250, "before write_rec FI=%d SessId=%d Strm=%s len=%d\n",
-         rec.FileIndex, rec.VolSessionId, stream_to_ascii(rec.Stream, rec.FileIndex),
+         rec.FileIndex, rec.VolSessionId, 
+         stream_to_ascii(buf1, rec.Stream, rec.FileIndex),
          rec.data_len);
 
       while (!write_record_to_block(block, &rec)) {
       }
       jcr->JobBytes += rec.data_len;   /* increment bytes this job */
       Dmsg4(190, "write_record FI=%s SessId=%d Strm=%s len=%d\n",
-         FI_to_ascii(rec.FileIndex), rec.VolSessionId,
-         stream_to_ascii(rec.Stream, rec.FileIndex), rec.data_len);
+         FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId,
+         stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len);
 
       /* Get out after writing 10 blocks to the second tape */
       if (BlockNumber > 10 && stop != 0) {      /* get out */
 
 #define ST_MEDIA           (1<<16)    /* Media found in mounted device */
 #define ST_OFFLINE         (1<<17)    /* set offline by operator */
 #define ST_PART_SPOOLED    (1<<18)    /* spooling part */
+#define ST_FREESPACE_OK    (1<<19)    /* Have valid freespace for DVD */
 
 /* dev_blocked states (mutually exclusive) */
 enum {
    uint32_t part;                     /* current part number (starts at 0) */
    uint64_t part_start;               /* current part start address (relative to the whole volume) */
    uint32_t num_parts;                /* number of parts WRITTEN on the DVD */
+   /* state ST_FREESPACE_OK is set if free_space is valid */
    uint64_t free_space;               /* current free space on medium (without the current part) */
-   int free_space_errno;              /* indicates:
-                                       * - free_space_errno == 0: ignore free_space.
-                                       * - free_space_errno < 0: an error occured. 
-                                       * - free_space_errno > 0: free_space is valid. */
+   int free_space_errno;              /* indicates errno getting freespace */
    bool truncating;                   /* if set, we are currently truncating the DVD */
    
    
    int at_eot() const { return state & ST_EOT; }
    int at_weot() const { return state & ST_WEOT; }
    int can_append() const { return state & ST_APPEND; }
+   int is_freespace_ok() const { return state & ST_FREESPACE_OK; }
    /*
     * can_write() is meant for checking at the end of a job to see
     * if we still have a tape (perhaps not if at end of tape
    void set_mounted() { state |= ST_MOUNTED; };
    void set_media() { state |= ST_MEDIA; };
    void set_short_block() { state |= ST_SHORT; };
+   void set_freespace_ok() { state |= ST_FREESPACE_OK; }
    void set_part_spooled(int val) { if (val) state |= ST_PART_SPOOLED; \
           else state &= ~ST_PART_SPOOLED; };
    void set_mounted(int val) { if (val) state |= ST_MOUNTED; \
    void clear_mounted() { state &= ~ST_MOUNTED; };
    void clear_media() { state &= ~ST_MEDIA; };
    void clear_short_block() { state &= ~ST_SHORT; };
+   void clear_freespace_ok() { state &= ~ST_FREESPACE_OK; }
 
    void block(int why); /* in dev.c */
    void unblock();      /* in dev.c */
 
    if (!icmd) {
       dev->free_space = 0;
       dev->free_space_errno = 0;
+      dev->clear_freespace_ok();   /* No valid freespace */
       dev->clear_media();
-      Dmsg2(29, "update_free_space_dev: free_space=%d, free_space_errno=%d (!icmd)\n", dev->free_space, dev->free_space_errno);
+      Dmsg2(29, "update_free_space_dev: free_space=%s, free_space_errno=%d (!icmd)\n", 
+            edit_uint64(dev->free_space, ed1), dev->free_space_errno);
       return;
    }
    
          free = str_to_int64(results);
          if (free >= 0) {
             dev->free_space = free;
-            dev->free_space_errno = 1;
+            dev->free_space_errno = 0;
+            dev->set_freespace_ok();     /* have valid freespace */
             dev->set_media();
             Mmsg0(dev->errmsg, "");
             break;
          }
       }
       dev->free_space = 0;
-      dev->free_space_errno = -EPIPE;
+      dev->free_space_errno = EPIPE;
+      dev->clear_freespace_ok();         /* no valid freespace */
       Mmsg1(dev->errmsg, _("Cannot run free space command (%s)\n"), results);
       
       if (--timeout > 0) {
          continue;
       }
 
-      dev->dev_errno = -dev->free_space_errno;
+      dev->dev_errno = dev->free_space_errno;
       Dmsg4(40, "Cannot get free space on device %s. free_space=%s, "
          "free_space_errno=%d ERR=%s\n",
             dev->print_name(), edit_uint64(dev->free_space, ed1),
    }
    
    free_pool_memory(results);
-   Dmsg3(29, "update_free_space_dev: free_space=%s, free_space_errno=%d have_media=%d\n", 
-      edit_uint64(dev->free_space, ed1), dev->free_space_errno, dev->have_media());
+   Dmsg4(29, "update_free_space_dev: free_space=%s freespace_ok=%d free_space_errno=%d have_media=%d\n", 
+      edit_uint64(dev->free_space, ed1), !!dev->is_freespace_ok(), dev->free_space_errno, !!dev->have_media());
    sm_check(__FILE__, __LINE__, false);
    return;
 }
 {
    DCR *dcr;
    off_t pos;
+   char ed1[50], ed2[50];
    
    Dmsg3(100, "Enter lseek_dev fd=%d part=%d nparts=%d\n", dev->fd,
       dev->part, dev->num_parts);
    dcr = (DCR *)dev->attached_dcrs->first();  /* any dcr will do */
    switch(whence) {
    case SEEK_SET:
-      Dmsg2(100, "lseek_dev SEEK_SET to %d (part_start=%d)\n", (int)offset, (int)dev->part_start);
+      Dmsg2(100, "lseek_dev SEEK_SET to %s (part_start=%s)\n",
+         edit_uint64(offset, ed1), edit_uint64(dev->part_start, ed2));
       if ((uint64_t)offset >= dev->part_start) {
          if (((uint64_t)offset == dev->part_start) || ((uint64_t)offset < (dev->part_start+dev->part_size))) {
             /* We are staying in the current part, just seek */
       }
       break;
    case SEEK_CUR:
-      Dmsg1(100, "lseek_dev SEEK_CUR to %d\n", (int)offset);
+      Dmsg1(100, "lseek_dev SEEK_CUR to %s\n", edit_uint64(offset, ed1));
       if ((pos = lseek(dev->fd, (off_t)0, SEEK_CUR)) < 0) {
          return pos;   
       }
       pos += dev->part_start;
       if (offset == 0) {
-         Dmsg1(100, "lseek_dev SEEK_CUR returns %d\n", pos);
+         Dmsg1(100, "lseek_dev SEEK_CUR returns %s\n", edit_uint64(pos, ed1));
          return pos;
       } else { /* Not used in Bacula, but should work */
          return lseek_dev(dev, pos, SEEK_SET);
       }
       break;
    case SEEK_END:
-      Dmsg1(100, "lseek_dev SEEK_END to %d\n", (int)offset);
+      Dmsg1(100, "lseek_dev SEEK_END to %s\n", edit_uint64(offset, ed1));
       /*
        * Bacula does not use offsets for SEEK_END
        *  Also, Bacula uses seek_end only when it wants to
        *  itself is read-only (as currently implemented).
        */
       if (offset > 0) { /* Not used by bacula */
-         Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %d\n", (int)offset);
+         Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %s\n", 
+            edit_uint64(offset, ed1));
          errno = EINVAL;
          return -1;
       }
          if ((pos = lseek(dev->fd, (off_t)0, SEEK_END)) < 0) {
             return pos;   
          } else {
-            Dmsg1(100, "lseek_dev SEEK_END returns %d\n", pos + dev->part_start);
+            Dmsg1(100, "lseek_dev SEEK_END returns %s\n", 
+                  edit_uint64(pos + dev->part_start, ed1));
             return pos + dev->part_start;
          }
       } else {
  *  %% = %
  *  %a = archive device name
  *  %e = erase (set if cannot mount and first part)
+ *  %n = part number
  *  %m = mount point
  *  %v = last part name
  *
             str = dev->dev_name;
             break;
          case 'e':
-            if (dev->part == 0) {
+            if (dev->num_parts == 0) {
                str = "1";
             } else {
                str = "0";
 
    struct date_time dt;
    DEVICE *dev = dcr->dev;
    JCR *jcr = dcr->jcr;
+   char buf[100];
 
    /* Serialize the label into the device record. */
 
    rec->VolSessionId = jcr->VolSessionId;
    rec->VolSessionTime = jcr->VolSessionTime;
    rec->Stream = jcr->NumVolumes;
-   Dmsg2(100, "Created Vol label rec: FI=%s len=%d\n", FI_to_ascii(rec->FileIndex),
+   Dmsg2(100, "Created Vol label rec: FI=%s len=%d\n", FI_to_ascii(buf, rec->FileIndex),
       rec->data_len);
 }
 
    DEVICE *dev = dcr->dev;
    DEV_RECORD *rec;
    DEV_BLOCK *block = dcr->block;
+   char buf1[100], buf2[100];
 
    rec = new_record();
    Dmsg1(90, "session_label record=%x\n", rec);
 
    Dmsg6(20, "Write sesson_label record JobId=%d FI=%s SessId=%d Strm=%s len=%d "
              "remainder=%d\n", jcr->JobId,
-      FI_to_ascii(rec->FileIndex), rec->VolSessionId,
-      stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len,
+      FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
+      stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
       rec->remainder);
 
    free_record(rec);
 bool unser_volume_label(DEVICE *dev, DEV_RECORD *rec)
 {
    ser_declare;
+   char buf1[100], buf2[100];
 
    if (rec->FileIndex != VOL_LABEL && rec->FileIndex != PRE_LABEL) {
       Mmsg3(dev->errmsg, _("Expecting Volume Label, got FI=%s Stream=%s len=%d\n"),
-              FI_to_ascii(rec->FileIndex),
-              stream_to_ascii(rec->Stream, rec->FileIndex),
+              FI_to_ascii(buf1, rec->FileIndex),
+              stream_to_ascii(buf2, rec->Stream, rec->FileIndex),
               rec->data_len);
       if (!forge_on) {
          return false;
 
 void     create_restore_volume_list(JCR *jcr);
 
 /* From record.c */
-const char *FI_to_ascii(int fi);
-const char *stream_to_ascii(int stream, int fi);
+const char *FI_to_ascii(char *buf, int fi);
+const char *stream_to_ascii(char *buf, int stream, int fi);
 bool        write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec);
 bool        can_write_record_to_block(DEV_BLOCK *block, DEV_RECORD *rec);
 bool        read_record_from_block(DEV_BLOCK *block, DEV_RECORD *rec);
 
  *   record as a Label, otherwise it is simply
  *   the FileIndex of the current file.
  */
-const char *FI_to_ascii(int fi)
+const char *FI_to_ascii(char *buf, int fi)
 {
-   static char buf[20];
    if (fi >= 0) {
       sprintf(buf, "%d", fi);
       return buf;
  *   dealing with a Label, hence the
  *   stream is the JobId.
  */
-const char *stream_to_ascii(int stream, int fi)
+const char *stream_to_ascii(char *buf, int stream, int fi)
 {
-    static char buf[20];
     if (fi < 0) {
        sprintf(buf, "%d", stream);
        return buf;
 {
    ser_declare;
    uint32_t remlen;
+   char buf1[100], buf2[100];
 
    remlen = block->buf_len - block->binbuf;
 
 
    Dmsg6(890, "write_record_to_block() FI=%s SessId=%d Strm=%s len=%d\n"
 "rem=%d remainder=%d\n",
-      FI_to_ascii(rec->FileIndex), rec->VolSessionId,
-      stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len,
+      FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
+      stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
       remlen, rec->remainder);
 
    /*
             /* We damaged a buffer */
             Dmsg6(0, "Damaged block FI=%s SessId=%d Strm=%s len=%d\n"
 "rem=%d remainder=%d\n",
-               FI_to_ascii(rec->FileIndex), rec->VolSessionId,
-               stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len,
+               FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
+               stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len,
                remlen, rec->remainder);
             Dmsg5(0, "Damaged block: bufp=%x binbuf=%d buf_len=%d rem=%d moved=%d\n",
                block->bufp, block->binbuf, block->buf_len, block->buf_len-block->binbuf,
    int32_t  Stream;
    uint32_t data_bytes;
    uint32_t rhl;
+   char buf1[100], buf2[100];
 
    remlen = block->binbuf;
    rec->Block = block->BlockNumber;
 
       Dmsg6(450, "rd_rec_blk() got FI=%s SessId=%d Strm=%s len=%u\n"
                  "remlen=%d data_len=%d\n",
-         FI_to_ascii(rec->FileIndex), rec->VolSessionId,
-         stream_to_ascii(rec->Stream, rec->FileIndex), data_bytes, remlen,
+         FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
+         stream_to_ascii(buf2, rec->Stream, rec->FileIndex), data_bytes, remlen,
          rec->data_len);
    } else {
       /*
    }
    rec->remainder = 0;
    Dmsg4(450, "Rtn full rd_rec_blk FI=%s SessId=%d Strm=%s len=%d\n",
-      FI_to_ascii(rec->FileIndex), rec->VolSessionId,
-      stream_to_ascii(rec->Stream, rec->FileIndex), rec->data_len);
+      FI_to_ascii(buf1, rec->FileIndex), rec->VolSessionId,
+      stream_to_ascii(buf2, rec->Stream, rec->FileIndex), rec->data_len);
    return true;                       /* transferred full record */
 }
 
 
 #undef  VERSION
 #define VERSION "1.37.41"
-#define BDATE   "12 October 2005"
-#define LSMDATE "12Oct05"
+#define BDATE   "13 October 2005"
+#define LSMDATE "13Oct05"
 
 /* Debug flags */
 #undef  DEBUG