/*
   Bacula(R) - The Network Backup Solution

   Copyright (C) 2000-2025 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.
*/
/*
 *
 *   dev.c  -- low level operations on device (storage device)
 *
 *     written by, Kern Sibbald, MM
 *
 *     NOTE!!!! None of these routines are reentrant. You must
 *        use dev->rLock() and dev->Unlock() at a higher level,
 *        or use the xxx_device() equivalents.  By moving the
 *        thread synchronization to a higher level, we permit
 *        the higher level routines to "seize" the device and
 *        to carry out operations without worrying about who
 *        set what lock (i.e. race conditions).
 *
 *     Note, this is the device dependent code, and may have
 *           to be modified for each system, but is meant to
 *           be as "generic" as possible.
 *
 *     The purpose of this code is to develop a SIMPLE Storage
 *     daemon. More complicated coding (double buffering, writer
 *     thread, ...) is left for a later version.
 */

/*
 * Handling I/O errors and end of tape conditions are a bit tricky.
 * This is how it is currently done when writing.
 * On either an I/O error or end of tape,
 * we will stop writing on the physical device (no I/O recovery is
 * attempted at least in this daemon). The state flag will be sent
 * to include ST_EOT, which is ephemeral, and ST_WEOT, which is
 * persistent. Lots of routines clear ST_EOT, but ST_WEOT is
 * cleared only when the problem goes away.  Now when ST_WEOT
 * is set all calls to write_block_to_device() call the fix_up
 * routine. In addition, all threads are blocked
 * from writing on the tape by calling lock_dev(), and thread other
 * than the first thread to hit the EOT will block on a condition
 * variable. The first thread to hit the EOT will continue to
 * be able to read and write the tape (he sort of tunnels through
 * the locking mechanism -- see lock_dev() for details).
 *
 * Now presumably somewhere higher in the chain of command
 * (device.c), someone will notice the EOT condition and
 * get a new tape up, get the tape label read, and mark
 * the label for rewriting. Then this higher level routine
 * will write the unwritten buffer to the new volume.
 * Finally, he will release
 * any blocked threads by doing a broadcast on the condition
 * variable.  At that point, we should be totally back in
 * business with no lost data.
 */

#include "bacula.h"
#include "stored.h"

#ifndef O_NONBLOCK
#define O_NONBLOCK 0
#endif

static const int dbglvl = 150;

/* Imported functions and variables */
extern void set_os_device_parameters(DCR *dcr);
extern bool dev_get_os_pos(DEVICE *dev, struct mtget *mt_stat);
extern uint32_t status_dev(DEVICE *dev);

/* Forward referenced functions */
const char *mode_to_str(int mode);
DEVICE *m_init_dev(JCR *jcr, DEVRES *device, bool adata);

/* To open device readonly when appropriate, like for bls for dedup devices */
int device_default_open_mode = omd_rdonly;

/*
 * Device specific initialization.
 */
int DEVICE::device_specific_init(JCR *jcr, DEVRES *device)
{
   return 0; // OK
}

int DEVICE::device_specific_close(DCR *dcr)
{
   int ret = 0;
   if (m_fd >= 0) {
      ret = d_close(m_fd) ;
      clear_opened();
   }
   return ret;
}

/*
 * Initialize the device with the operating system and
 * initialize buffer pointers.
 *
 * Returns:  true  device already open
 *           false device setup but not open
 *
 * Note, for a tape, the VolName is the name we give to the
 *    volume (not really used here), but for a file, the
 *    VolName represents the name of the file to be created/opened.
 *    In the case of a file, the full name is the device name
 *    (archive_name) with the VolName concatenated.
 *
 * This is generic common code. It should be called prior to any
 *     device specific code.  Note! This does not open anything.
 */
bool DEVICE::open_device(DCR *dcr, int omode)
{
   Enter(dbglvl);
   preserve = 0;
   ASSERT2(!adata, "Attempt to open adata dev");
   if (is_open()) {
      if (openmode == omode) {
         Leave(dbglvl);
         return true;
      } else {
         Dmsg1(200, "Close fd=%d for mode change in open().\n", m_fd);
         device_specific_close(dcr);
         preserve = state & (ST_LABEL|ST_APPEND|ST_READ);
      }
   }
   openmode = omode;
   if (dcr) {
      dcr->setVolCatName(dcr->VolumeName);
      VolCatInfo = dcr->VolCatInfo;    /* structure assign */
   }

   state &= ~(ST_NOSPACE|ST_LABEL|ST_APPEND|ST_READ|ST_EOT|ST_WEOT|ST_EOF);
   label_type = B_BACULA_LABEL;

   if (openmode == OPEN_READ_WRITE && has_cap(CAP_STREAM)) {
      openmode = OPEN_WRITE_ONLY;
   }
   Leave(dbglvl);
   return false;
}

void DEVICE::set_mode(int new_mode)
{
   switch (new_mode) {
   case CREATE_READ_WRITE:
      mode = O_CREAT | O_RDWR | O_BINARY;
      break;
   case OPEN_READ_WRITE:
      mode = O_RDWR | O_BINARY;
      break;
   case OPEN_READ_ONLY:
      mode = O_RDONLY | O_BINARY;
      break;
   case OPEN_WRITE_ONLY:
      mode = O_WRONLY | O_BINARY;
      break;
   default:
      Jmsg0(NULL, M_ABORT, 0, _("Illegal mode given to open dev.\n"));
   }
}

/*
 * Called to indicate that we have just read an
 *  EOF from the device.
 */
void DEVICE::set_ateof()
{
   set_eof();
   file_addr = 0;
   set_file_size(0);
   block_num = 0;
}

/*
 * Called to indicate we are now at the end of the tape, and
 *   writing is not possible.
 */
void DEVICE::set_ateot()
{
   /* Make tape effectively read-only */
   Dmsg0(200, "==== Set AtEof\n");
   state |= (ST_EOF|ST_EOT|ST_WEOT);
   clear_append();
}


/*
 * Set the position of the device -- only for files
 *   For other devices, there is no generic way to do it.
 *  Returns: true  on succes
 *           false on error
 */
bool DEVICE::update_pos(DCR *dcr)
{
   boffset_t pos;
   bool ok = true;

   if (!is_open()) {
      dev_errno = EBADF;
      Mmsg0(errmsg, _("Bad device call. Device not open\n"));
      Emsg1(M_FATAL, 0, "%s", errmsg);
      return false;
   }

   if (is_file()) {
      file = 0;
      file_addr = 0;
      pos = lseek(dcr, (boffset_t)0, SEEK_CUR);
      if (pos < 0) {
         berrno be;
         dev_errno = errno;
         Pmsg1(000, _("Seek error: ERR=%s\n"), be.bstrerror());
         Mmsg2(errmsg, _("lseek error on %s. ERR=%s.\n"),
               print_name(), be.bstrerror());
         ok = false;
      } else {
         file_addr = pos;
         block_num = (uint32_t)pos;
         file = (uint32_t)(pos >> 32);
      }
   }
   return ok;
}

void DEVICE::set_slot(int32_t slot)
{
   m_slot = slot;
   if (vol) vol->clear_slot();
}

void DEVICE::clear_slot()
{
   m_slot = -1;
   if (vol) vol->set_slot(-1);
}

/*
 * Set to unload the current volume in the drive
 */
void DEVICE::set_unload()
{
   if (!m_unload && VolHdr.VolumeName[0] != 0) {
       m_unload = true;
       notify_newvol_in_attached_dcrs(NULL);
   }
}

/*
 * Clear volume header
 */
void DEVICE::clear_volhdr()
{
   Dmsg1(100, "Clear volhdr vol=%s\n", VolHdr.VolumeName);
   memset(&VolHdr, 0, sizeof(VolHdr));
   setVolCatInfo(false);
}

void DEVICE::set_volcatinfo_from_dcr(DCR *dcr)
{
   VolCatInfo = dcr->VolCatInfo;
}

bool DEVICE::sync_data(DCR *dcr)
{
   int ret=true;

#ifndef HAVE_WIN32
   if (!has_cap(CAP_SYNCONCLOSE)) {
       return true;
   }
   if (!is_open()) {
      Dmsg2(200, "device %s closed vol=%s\n", print_name(),
            VolHdr.VolumeName);
      return true;                    /* already closed */
   }

   while ((ret = fsync(m_fd)) < 0 && errno == EINTR) {
      bmicrosleep(0, 5000);
   }

   if (ret < 0) {
      berrno be;
      dev_errno = errno;
      Mmsg(errmsg, _("Error syncing volume \"%s\" on device %s. ERR=%s."),
           VolHdr.VolumeName, print_name(), be.bstrerror());
      return false;
   }
   ret = true;
#endif
   return ret;
}

/*
 * Close the device
 *   Can enter with dcr==NULL
 */
bool DEVICE::close(DCR *dcr)
{
   bool ok = true;

   Dmsg5(40, "close_dev vol=%s fd=%d dev=%p adata=%d dev=%s\n",
      VolHdr.VolumeName, m_fd, this, adata, print_name());
   offline_or_rewind(dcr);

   if (!is_open()) {
      Dmsg2(200, "device %s already closed vol=%s\n", print_name(),
         VolHdr.VolumeName);
      return true;                    /* already closed */
   }

   switch (dev_type) {
   case B_VTL_DEV:
   case B_VTAPE_DEV:
   case B_TAPE_DEV:
      unlock_door();
      /* Fall through wanted */
   default:
      if (d_close(m_fd) != 0) {
         berrno be;
         dev_errno = errno;
         Mmsg(errmsg, _("Error closing volume \"%s\" device %s. ERR=%s.\n"),
              VolHdr.VolumeName, print_name(), be.bstrerror());
         ok = false;
      }
      break;
   }

   unmount(1);                       /* do unmount if required */

   /* Clean up device packet so it can be reused */
   clear_opened();

   /*
    * Only when SAN shared storage configured,
    *   when the device is unloaded, the slot information
    *   can be incorrect, so clear the slot.
    */
   /* ***BEEF ***/
   if (is_tape() && device->lock_command && device->control_name) {
      clear_slot();
   }

   state &= ~(ST_LABEL|ST_READ|ST_APPEND|ST_EOT|ST_WEOT|ST_EOF|
              ST_NOSPACE|ST_MOUNTED|ST_MEDIA|ST_SHORT);
   label_type = B_BACULA_LABEL;
   file = block_num = 0;
   set_file_size(0);
   file_addr = 0;
   EndFile = EndBlock = 0;
   openmode = 0;
   clear_volhdr();
   memset(&VolCatInfo, 0, sizeof(VolCatInfo));
   if (tid) {
      stop_thread_timer(tid);
      tid = 0;
   }
   return ok;
}

/*
 * If timeout, wait until the mount command returns 0.
 * If !timeout, try to mount the device only once.
 */
bool DEVICE::mount(int timeout)
{
   Enter(dbglvl);
   if (!is_mounted() && device->mount_command) {
      Leave(dbglvl);
      return mount_file(1, timeout);
   }
   Leave(dbglvl);
   return true;
}

/*
 * Unmount the device
 * If timeout, wait until the unmount command returns 0.
 * If !timeout, try to unmount the device only once.
 */
bool DEVICE::unmount(int timeout)
{
   Enter(dbglvl);
   if (is_mounted() && requires_mount() && device->unmount_command) {
      Leave(dbglvl);
      return mount_file(0, timeout);
   }
   Leave(dbglvl);
   return true;
}


/*
 * Edit codes into (Un)MountCommand, Write(First)PartCommand
 *  %% = %
 *  %a = archive device name
 *  %e = erase (set if cannot mount and first part)
 *  %n = part number
 *  %m = mount point
 *  %v = last part name
 *
 *  omsg = edited output message
 *  imsg = input string containing edit codes (%x)
 *
 */
void DEVICE::edit_mount_codes(POOL_MEM &omsg, const char *imsg)
{
   const char *p;
   const char *str;
   char add[20];

   POOL_MEM archive_name(PM_FNAME);

   omsg.c_str()[0] = 0;
   Dmsg1(800, "edit_mount_codes: %s\n", imsg);
   for (p=imsg; *p; p++) {
      if (*p == '%') {
         switch (*++p) {
         case '%':
            str = "%";
            break;
         case 'a':
            str = dev_name;
            break;
         case 'e':
            str = "0";
            /* ***FIXME*** this may be useful for Cloud */
#ifdef xxx
            if (num_dvd_parts == 0) {
               if (truncating || blank_dvd) {
                  str = "2";
               } else {
                  str = "1";
               }
            } else {
               str = "0";
            }
#endif
            break;
         case 'n':
            bsnprintf(add, sizeof(add), "%d", part);
            str = add;
            break;
         case 'm':
            str = device->mount_point;
            break;
         default:
            add[0] = '%';
            add[1] = *p;
            add[2] = 0;
            str = add;
            break;
         }
      } else {
         add[0] = *p;
         add[1] = 0;
         str = add;
      }
      Dmsg1(1900, "add_str %s\n", str);
      pm_strcat(omsg, (char *)str);
      Dmsg1(1800, "omsg=%s\n", omsg.c_str());
   }
}

/* return the last timer interval (ms)
 * or 0 if something goes wrong
 */
btime_t DEVICE::get_timer_count()
{
   btime_t temp = last_timer;
   last_timer = get_current_btime();
   temp = last_timer - temp;   /* get elapsed time */
   return (temp>0)?temp:0;     /* take care of skewed clock */
}

/* read from fd */
ssize_t DEVICE::read(void *buf, size_t len)
{
   ssize_t read_len;
   ssize_t stat_read_len = 0;

   get_timer_count();

   read_len = d_read(m_fd, buf, len);

   last_tick = get_timer_count();

   DevReadTime += last_tick;
   VolCatInfo.VolReadTime += last_tick;

   if (read_len > 0) {          /* skip error */
      DevReadBytes += read_len;
      stat_read_len = read_len;
   }

   collector_update_add2_value_int64(devstatcollector, devstatmetrics.bacula_storage_device_readbytes, stat_read_len,
         devstatmetrics.bacula_storage_device_readtime, last_tick);

   return read_len;
}

/* write to fd */
ssize_t DEVICE::write(const void *buf, size_t len)
{
   ssize_t write_len;
   ssize_t stat_write_len = 0;

   get_timer_count();

   write_len = d_write(m_fd, buf, len);

   last_tick = get_timer_count();

   DevWriteTime += last_tick;
   VolCatInfo.VolWriteTime += last_tick;

   if (write_len > 0) {         /* skip error */
      DevWriteBytes += write_len;
      stat_write_len = write_len;
   }

   collector_update_add2_value_int64(devstatcollector, devstatmetrics.bacula_storage_device_writebytes, stat_write_len,
         devstatmetrics.bacula_storage_device_writetime, last_tick);

   return write_len;
}

/* to make the difference with an "real" auto changer */
bool DEVICE::is_virtual_autochanger() const {
   return device->changer_command &&
      (device->changer_command[0] == 0 ||
       strcmp(device->changer_command, "/dev/null") == 0);
}

/* Return the resource name for the device */
const char *DEVICE::name() const
{
   return device->hdr.name;
}

uint32_t DEVICE::get_file()
{
   if (is_tape()) {
      return file;
   } else {
      uint64_t bytes = VolCatInfo.VolCatAdataBytes + VolCatInfo.VolCatAmetaBytes;
      return (uint32_t)(bytes >> 32);
   }
}

uint32_t DEVICE::get_block_num()
{
   if (is_tape()) {
      return block_num;
   } else {
      return  VolCatInfo.VolCatAdataBlocks + VolCatInfo.VolCatAmetaBlocks;
   }
}

/*
 * Walk through all attached jcrs indicating the volume has changed
 *   Note: If you have the new VolumeName, it is passed here,
 *     otherwise pass a NULL.
 */
void
DEVICE::notify_newvol_in_attached_dcrs(const char *newVolumeName)
{
   Dmsg2(140, "Notify dcrs of vol change. oldVolume=%s NewVolume=%s\n",
      getVolCatName(), newVolumeName?newVolumeName:"*None*");
   Lock_dcrs();
   DCR *mdcr;
   foreach_dlist(mdcr, attached_dcrs) {
      if (mdcr->jcr->JobId == 0) {
         continue;                 /* ignore console */
      }
      mdcr->NewVol = true;
      mdcr->NewFile = true;
      if (newVolumeName && mdcr->VolumeName != newVolumeName) {
         bstrncpy(mdcr->VolumeName, newVolumeName, sizeof(mdcr->VolumeName));
         Dmsg2(140, "Set NewVol=%s in JobId=%d\n", mdcr->VolumeName, mdcr->jcr->JobId);
      }
   }
   Unlock_dcrs();
}

/*
 * Walk through all attached jcrs indicating the File has changed
 */
void
DEVICE::notify_newfile_in_attached_dcrs()
{
   Dmsg1(140, "Notify dcrs of file change. Volume=%s\n", getVolCatName());
   Lock_dcrs();
   DCR *mdcr;
   foreach_dlist(mdcr, attached_dcrs) {
      if (mdcr->jcr->JobId == 0) {
         continue;                 /* ignore console */
      }
      Dmsg1(140, "Notify JobI=%d\n", mdcr->jcr->JobId);
      mdcr->NewFile = true;
   }
   Unlock_dcrs();
}



/*
 * Free memory allocated for the device
 *  Can enter with dcr==NULL
 */
void DEVICE::term(DCR *dcr)
{
   Dmsg1(900, "term dev: %s\n", print_name());
   if (!dcr) {
      d_close(m_fd);
   } else {
      close(dcr);
   }
   if (dev_name) {
      free_memory(dev_name);
      dev_name = NULL;
   }
   if (adev_name) {
      free_memory(adev_name);
      adev_name = NULL;
   }
   if (prt_name) {
      free_memory(prt_name);
      prt_name = NULL;
   }
   if (errmsg) {
      free_pool_memory(errmsg);
      errmsg = NULL;
   }
   pthread_mutex_destroy(&m_mutex);
   pthread_cond_destroy(&wait);
   pthread_cond_destroy(&wait_next_vol);
   pthread_mutex_destroy(&spool_mutex);
   pthread_mutex_destroy(&freespace_mutex);
   if (attached_dcrs) {
      delete attached_dcrs;
      attached_dcrs = NULL;
   }
   /* We let the DEVRES pointer if not our device */
   if (device && device->dev == this) {
      device->dev = NULL;
   }
   if (crypto_device_ctx) {
      block_cipher_context_free(crypto_device_ctx);
      crypto_device_ctx = NULL;
   }
   delete this;
}

/* Get freespace values */
void DEVICE::get_freespace(uint64_t *freeval, uint64_t *totalval)
{
   if (get_os_device_freespace()) {
      P(freespace_mutex);
      if (is_freespace_ok()) {
         *freeval = free_space;
         *totalval = total_space;
      } else {
         *freeval = *totalval = 0;
      }
      V(freespace_mutex);
   }
}

/* Set freespace values */
void DEVICE::set_freespace(uint64_t freeval, uint64_t totalval, int errnoval, bool valid)
{
   P(freespace_mutex);
   free_space = freeval;
   total_space = totalval;
   free_space_errno = errnoval;
   if (valid) {
      set_freespace_ok();
   } else {
      clear_freespace_ok();
   }
   V(freespace_mutex);
}

/* Convenient function that return true only  if the device back-end is a
 * filesystem that its nearly full. (the free space is below the given threshold)
 */
bool DEVICE::is_fs_nearly_full(uint64_t threshold)
{
   return false;
}

static const char *modes[] = {
   "CREATE_READ_WRITE",
   "OPEN_READ_WRITE",
   "OPEN_READ_ONLY",
   "OPEN_WRITE_ONLY"
};


const char *mode_to_str(int mode)
{
   static char buf[100];
   if (mode < 1 || mode > 4) {
      bsnprintf(buf, sizeof(buf), "BAD mode=%d", mode);
      return buf;
    }
   return modes[mode-1];
}

void DEVICE::setVolCatName(const char *name)
{
   bstrncpy(VolCatInfo.VolCatName, name, sizeof(VolCatInfo.VolCatName));
   setVolCatInfo(false);
}

void DEVICE::setVolCatStatus(const char *status)
{
   bstrncpy(VolCatInfo.VolCatStatus, status, sizeof(VolCatInfo.VolCatStatus));
   setVolCatInfo(false);
}

void DEVICE::updateVolCatBytes(uint64_t bytes)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaBytes += bytes;
   VolCatInfo.VolCatBytes += bytes;
   VolCatInfo.BytesWritten += bytes; /* We keep the count of written bytes since the last catalog update */
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}

/* Used by subclass like dedup to take account of bytes going into dedup.
 * The accounting is expected to be done in VolCatAdataBytes & VolCatBytes
 */
void DEVICE::updateVolCatExtraBytes(uint64_t bytes)
{
}

/*
 * Note, for us a hole is bytes skipped. The OS
 *  may have a different idea of a hole due to
 *  the filesystem block size.
 */
void DEVICE::updateVolCatHoleBytes(uint64_t hole)
{
   return;
}

void DEVICE::updateVolCatPadding(uint64_t padding)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaPadding += padding;
   VolCatInfo.VolCatPadding += padding;
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}


void DEVICE::updateVolCatBlocks(uint32_t blocks)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaBlocks += blocks;
   VolCatInfo.VolCatBlocks += blocks;
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}

void DEVICE::updateVolCatWrites(uint32_t writes)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaWrites += writes;
   VolCatInfo.VolCatWrites += writes;
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}

void DEVICE::updateVolCatReads(uint32_t reads)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaReads += reads;
   VolCatInfo.VolCatReads += reads;
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}

void DEVICE::updateVolCatReadBytes(uint64_t bytes)
{
   Lock_VolCatInfo();
   VolCatInfo.VolCatAmetaRBytes += bytes;
   VolCatInfo.VolCatRBytes += bytes;
   setVolCatInfo(false);
   Unlock_VolCatInfo();
}

void DEVICE::set_nospace()
{
   state |= ST_NOSPACE;
}

void DEVICE::clear_nospace()
{
   state &= ~ST_NOSPACE;
}

/* Put device in append mode */
void DEVICE::set_append()
{
   state &= ~(ST_NOSPACE|ST_READ|ST_EOT|ST_EOF|ST_WEOT);  /* remove EOF/EOT flags */
   state |= ST_APPEND;
}

/* Clear append mode */
void DEVICE::clear_append()
{
   state &= ~ST_APPEND;
}

/* Put device in read mode */
void DEVICE::set_read()
{
   state &= ~(ST_APPEND|ST_EOT|ST_EOF|ST_WEOT);  /* remove EOF/EOT flags */
   state |= ST_READ;
}

/* Clear read mode */
void DEVICE::clear_read()
{
   state &= ~ST_READ;
}

/*
 * Get freespace using OS calls
 * TODO: See if it's working with mount commands
 */
bool DEVICE::get_os_device_freespace()
{
   return false;
}

/* Update the free space on the device */
bool DEVICE::update_freespace()
{
   POOL_MEM ocmd(PM_FNAME);
   POOLMEM* results;
   char* icmd;
   char* p;
   uint64_t free, total;
   char ed1[50];
   bool ok = false;
   int status;
   berrno be;

   if (!is_file()) {
      Mmsg(errmsg, "");
      return true;
   }

   /* The device must be mounted in order for freespace to work */
   if (requires_mount()) {
      mount(1);
   }

   if (get_os_device_freespace()) {
      Dmsg4(20, "get_os_device_freespace: free_space=%s freespace_ok=%d free_space_errno=%d have_media=%d\n",
         edit_uint64(free_space, ed1), !!is_freespace_ok(), free_space_errno, !!have_media());
      return true;
   }

   icmd = device->free_space_command;

   if (!icmd) {
      set_freespace(0, 0, 0, false);
      Dmsg2(20, "ERROR: update_free_space_dev: free_space=%s, free_space_errno=%d (!icmd)\n",
            edit_uint64(free_space, ed1), free_space_errno);
      Mmsg(errmsg, _("No FreeSpace command defined.\n"));
      return false;
   }

   edit_mount_codes(ocmd, icmd);

   Dmsg1(20, "update_freespace: cmd=%s\n", ocmd.c_str());

   results = get_pool_memory(PM_MESSAGE);

   Dmsg1(20, "Run freespace prog=%s\n", ocmd.c_str());
   status = run_program_full_output(ocmd.c_str(), max_open_wait/2, results);
   Dmsg2(20, "Freespace status=%d result=%s\n", status, results);
   /* Should report "1223232 12323232\n"  "free  total\n" */
   if (status == 0) {
      free = str_to_int64(results) * 1024;
      p = results;

      if (skip_nonspaces(&p)) {
         total = str_to_int64(p) * 1024;

      } else {
         total = 0;
      }

      Dmsg1(400, "Free space program run: Freespace=%s\n", results);
      if (free >= 0) {
         set_freespace(free, total, 0, true); /* have valid freespace */
         Mmsg(errmsg, "");
         ok = true;
      }
   } else {
      set_freespace(0, 0, EPIPE, false); /* no valid freespace */
      Mmsg2(errmsg, _("Cannot run free space command. Results=%s ERR=%s\n"),
            results, be.bstrerror(status));

      dev_errno = free_space_errno;
      Dmsg4(20, "Cannot get free space on device %s. free_space=%s, "
         "free_space_errno=%d ERR=%s\n",
            print_name(), edit_uint64(free_space, ed1),
            free_space_errno, errmsg);
   }
   free_pool_memory(results);
   Dmsg4(20, "leave update_freespace: free_space=%s freespace_ok=%d free_space_errno=%d have_media=%d\n",
      edit_uint64(free_space, ed1), !!is_freespace_ok(), free_space_errno, !!have_media());
   return ok;
}

bool DEVICE::weof(DCR */*dcr*/, int num)
{
   Dmsg1(129, "=== weof_dev=%s\n", print_name());

   if (!is_open()) {
      dev_errno = EBADF;
      Mmsg1(errmsg, _("Bad call to weof_dev. Device %s not open\n"), print_name());
      Emsg0(M_FATAL, 0, errmsg);
      return false;
   }

   if (!can_append()) {
      Mmsg1(errmsg, _("Attempt to WEOF on non-appendable Volume %s\n"), VolHdr.VolumeName);
      Emsg0(M_FATAL, 0, errmsg);
      return false;
   }

   set_file_size(0);
   return true;
}

/*
 * Very basic functions -- no device specific code.
 *  Returns: true  on succes
 *           false on error
 */
bool DEVICE::eod(DCR *dcr)
{
   bool ok = true;

   Enter(dbglvl);
   if (m_fd < 0) {
      dev_errno = EBADF;
      Mmsg1(errmsg, _("Bad call to eod. Device %s not open\n"), print_name());
      Dmsg1(100, "%s", errmsg);
      Leave(dbglvl);
      return false;
   }

   if (at_eot()) {
      Leave(100);
      return true;
   }
   clear_eof();         /* remove EOF flag */
   block_num = file = 0;
   set_file_size(0);
   file_addr = 0;
   Leave(100);
   return ok;
}

bool DEVICE::is_eod_valid(DCR *dcr)
{
   return true;
}

bool DEVICE::open_next_part(DCR */*dcr*/)
{
   return true;
}

bool DEVICE::close_part(DCR */*dcr*/)
{
   return true;
}

DEVICE *DEVICE::get_dev(DCR */*dcr*/)
{
   return this;
}

uint32_t DEVICE::get_hi_addr()
{
   return (uint32_t)(file_addr >> 32);
}

uint32_t DEVICE::get_hi_addr(boffset_t addr)
{
   return (uint32_t)(addr >> 32);
}

uint32_t DEVICE::get_low_addr()
{
   return (uint32_t)(file_addr);
}

uint32_t DEVICE::get_low_addr(boffset_t addr)
{
   return (uint32_t)(addr);
}


uint64_t DEVICE::get_full_addr()
{
   return file_addr;
}

uint64_t DEVICE::get_full_addr(boffset_t addr)
{
   return addr;
}


uint64_t DEVICE::get_full_addr(uint32_t hi, uint32_t low)
{
   return ((uint64_t)hi)<<32 | (uint64_t)low;
}

/* Note: this subroutine is not in the class */
uint64_t get_full_addr(uint32_t hi, uint32_t low)
{
   return ((uint64_t)hi)<<32 | (uint64_t)low;
}

/* Print the file address */
char *DEVICE::print_addr(char *buf, int32_t buf_len)
{
   buf[0] = 0;
   bsnprintf(buf, buf_len, "%llu", get_full_addr());
   return buf;
}

char *DEVICE::print_addr(char *buf, int32_t buf_len, boffset_t addr)
{
   buf[0] = 0;
   bsnprintf(buf, buf_len, "%llu", addr);
   return buf;
}


bool DEVICE::do_size_checks(DCR *dcr, DEV_BLOCK *block)
{
   JCR *jcr = dcr->jcr;
   if (is_pool_size_reached(dcr, true)) {
      if (!dir_get_pool_info(dcr, &VolCatInfo)) {
         Dmsg0(50, "Error updating volume info.\n");
      }
   }

   if (is_user_volume_size_reached(dcr, true)) {
      Dmsg0(50, "Calling terminate_writing_volume\n");
      terminate_writing_volume(dcr);
      reread_last_block(dcr);   /* Only used on tapes */
      dev_errno = ENOSPC;
      return false;
   }

   /*
    * Limit maximum File size on volume to user specified value.
    *  In practical terms, this means to put an EOF mark on
    *  a tape after every X bytes. This effectively determines
    *  how many index records we have (JobMedia). If you set
    *  max_file_size too small, it will cause a lot of shoe-shine
    *  on very fast modern tape (LTO-3 and above).
    */
   if ((max_file_size > 0) &&
       (file_size+block->binbuf) >= max_file_size) {
      set_file_size(0);             /* reset file size */

      if (!weof(dcr, 1)) {            /* write eof */
         Dmsg0(50, "WEOF error in max file size.\n");
         Jmsg(jcr, M_FATAL, 0, _("Unable to write EOF. ERR=%s\n"),
            bstrerror());
         Dmsg0(40, "Calling terminate_writing_volume\n");
         terminate_writing_volume(dcr);
         dev_errno = ENOSPC;
         return false;
      }

      if (!do_new_file_bookkeeping(dcr)) {
         /* Error message already sent */
         return false;
      }
   }
   return true;
}

bool DEVICE::get_tape_alerts(DCR *dcr)
{
   return true;
}

void DEVICE::show_tape_alerts(DCR *dcr, alert_list_type type,
       alert_list_which which, alert_cb alert_callback)
{
   return;
}

int DEVICE::delete_alerts()
{
   return 0;
}

// TODO: fill metrics description
void DEVICE::register_metrics(bstatcollect *collector)
{
   POOL_MEM met(PM_NAME);

   devstatcollector = collector;
   if (!collector){
      return;
   }
   Dmsg2(100, "DEVICE::register_metrics called. 0x%p collector=0x%p\n", this, collector);
   Mmsg(met, "bacula.storage.%s.device.%s.readbytes", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_readbytes =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
            (char*)"The number of bytes read from device.");
   Mmsg(met, "bacula.storage.%s.device.%s.readtime", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_readtime =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_MSEC,
            (char*)"Time spent reading from device.");
   Mmsg(met, "bacula.storage.%s.device.%s.readspeed", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_readspeed =
         devstatcollector->registration(met.c_str(), METRIC_FLOAT, METRIC_UNIT_BYTESEC,
            (char*)"Device read throughput.");
   Mmsg(met, "bacula.storage.%s.device.%s.writespeed", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_writespeed =
         devstatcollector->registration(met.c_str(), METRIC_FLOAT, METRIC_UNIT_BYTESEC,
            (char*)"Device write throughput.");
   Mmsg(met, "bacula.storage.%s.device.%s.status", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_status =
         devstatcollector->registration_bool(met.c_str(), METRIC_UNIT_STATUS, enabled,
            (char*)"Show if device is enabled (True/1) or disabled (False/0).");
   Mmsg(met, "bacula.storage.%s.device.%s.writebytes", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_writebytes =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
            (char*)"The number of bytes written to device.");
   Mmsg(met, "bacula.storage.%s.device.%s.writetime", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_writetime =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_MSEC,
            (char*)"Time spent writing to device.");
   /* total and free space metrics registration */
   Mmsg(met, "bacula.storage.%s.device.%s.freespace", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_freespace =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
            (char*)"The size of available space of the disk storage for device (could be shared).");
   Mmsg(met, "bacula.storage.%s.device.%s.totalspace", me->hdr.name, name());
   devstatmetrics.bacula_storage_device_totalspace =
         devstatcollector->registration(met.c_str(), METRIC_INT, METRIC_UNIT_BYTE,
            (char*)"The size of the disk storage for device (could be shared).");
};
bool DEVICE::get_tape_worm(DCR *dcr)
{
   return false;
}

int DEVICE::use_volume_encryption()
{
   return device->volume_encryption != ET_NO;
};

bool DEVICE::load_encryption_key(DCR *dcr, const char *operation,
      const char *volume_name,
      uint32_t *enc_cipher_key_size, unsigned char *enc_cipher_key,
      uint32_t *master_keyid_size, unsigned char *master_keyid)
{
   JCR *jcr = dcr->jcr;
   enum { op_none, op_label, op_read };
   bool ok = true; // No error
   Dmsg4(100, "load_encryption_key %s %s enc=%ld ver=%d\n", operation, volume_name, device->volume_encryption, VolHdr.BlockVer);
   int op = op_none;
   if (0 == strcmp(operation, "LABEL")) {
      op = op_label;
   } else if (0 == strcmp(operation, "READ")) {
      op = op_read;
   }
   /* don't use encryption if volume encryption is not enable or we are reading
    * (aka not recycling) a BB02 volume */
   if (device->volume_encryption == ET_NO
        && (op != op_label && (VolHdr.blkh_options & BLKHOPT_ENCRYPT_VOL))) {
      /* we expect an error later */
      Jmsg1(jcr, M_WARNING, 0, _("Trying to read encrypted volume \"%s\" on an un-encrypted device\n"), volume_name);
   }
   if (device->volume_encryption == ET_NO
        || (op != op_label && !(VolHdr.blkh_options & BLKHOPT_ENCRYPT_VOL))) {
      return true; /* No error */
   }
   if (me->encryption_command == NULL || me->encryption_command[0] == '\0') {
      if (jcr == NULL || !jcr->is_job_canceled()) {
         Jmsg0(jcr, M_FATAL, 0, _("The \"Encryption Command\" is empty, impossible to call the key-manager program\n"));
      }
      return false; /* error */
   }
   POOLMEM *encrypt_program = get_pool_memory(PM_FNAME);
   POOL_MEM results(PM_MESSAGE);
   POOL_MEM err_msg(PM_MESSAGE);
   POOL_MEM envv;

   edit_device_codes(dcr, &encrypt_program, me->encryption_command, "load");
   char *envp[5];
   Mmsg(envv, "OPERATION=%s", operation);
   envp[0] = bstrdup(envv.c_str());
   Mmsg(envv, "VOLUME_NAME=%s", volume_name);
   envp[1] = bstrdup(envv.c_str());
   if (op == op_read && enc_cipher_key != NULL && *enc_cipher_key_size > 0) {
      char buf[2*MAX_ENC_CIPHER_KEY_LEN]; // for the base64 encoded enc_cipherkey
      bin_to_base64_pad(buf, sizeof(buf), (char *)enc_cipher_key, *enc_cipher_key_size);
      Mmsg(envv, "ENC_CIPHER_KEY=%s", buf);
   } else {
      Mmsg(envv, "ENC_CIPHER_KEY=");
   }
   envp[2] = bstrdup(envv.c_str());
   if (op == op_read && master_keyid != NULL && *master_keyid_size > 0) {
      char buf[2*MAX_MASTERKEY_ID_LEN]; // for the base64 encoded masterkey_id
      bin_to_base64_pad(buf, sizeof(buf), (char *)master_keyid, *master_keyid_size);
      Mmsg(envv, "MASTER_KEYID=%s", buf);
   } else {
      Mmsg(envv, "MASTER_KEYID=");
   }
   envp[3] = bstrdup(envv.c_str());
   envp[4] = NULL;

   Dmsg3(60, "Run keymanager op=%s volume=%s %s\n", operation, volume_name, encrypt_program);
   for (char **p=envp; *p!=NULL; p++) Dmsg1(200, "keymanager query %s\n", *p);

   uint32_t timeout = 60;
   int status = run_program_full_output(encrypt_program, timeout, results.addr(), envp);
   free_pool_memory(encrypt_program);
   for (unsigned i = 0; i < sizeof(envp)/sizeof(char *)-1; i++)
   {
      bfree(envp[i]);
   }

   block_cipher_type cipher = BLOCK_CIPHER_NONE;
   const char *cipher_name = "undefined";
   int cipher_key_size = 0; /* from the cipher */
   char *in_cipher_key = NULL;
   int in_cipher_key_size = 0;
   char *in_enc_cipher_key = NULL;
   int in_enc_cipher_key_size = 0;
   int in_master_keyid_size = 0;
   char *in_master_keyid = NULL;
   char *comment = NULL;
   char keybuf[4096], enckeybuf[4096], masterkeyidbuf[4096];
   if (status == 0) {
      if (chk_dbglvl(200)) {
         /* display the response of the key manager */
         char *s = results.c_str();
         while (*s != '\0') {
            char *e = strchr(s, '\n');
            if (e != NULL) {
               char c = *e;
               *e = '\0';
               Dmsg1(200, "keymanager response %s\n", s);
               *e = c;
               s = e + 1;
            }
         }
      }
      /* iterate line to retrieve field */
      char *p = results.c_str();
      char *fieldname = NULL;
      char *value = NULL;
      char *end;
      while (*p != '\0') {
         while (isspace(*p)) p++;
         fieldname = p; /* start of the fieldname */
         while (isalnum(*p) || *p == '_') p++;
         if (fieldname == p) {
            /* fieldname is empty, EOF */
            break;
         }
         if (isblank(*p) || *p=='=' || *p==':') {
            end = p;
         } else {
            Dmsg1(10, "keymanager response format mismatch at %d\n", p-results.c_str());
            Mmsg(err_msg, "line format mismatch");
            break;
         }
         while (isblank(*p)) p++; /* skip blank just before the separator */
         if (*p != '=' && *p != ':') {
            Dmsg2(10, "keymanager response wrong separator %d %d\n", p-results.c_str(), *p);
            Mmsg(err_msg, "wrong separator");
            break;
         }
         p++; /* skip the separator */
         while (isblank(*p)) p++; /* skip blank just after the separator */
         *end = '\0'; /* end the fieldname */
         value = p; /* start of the value */
         while (*p && *p != '\n') p++;
         if (*p != '\0') {
            *p = '\0';
            p++;
         }
         Dmsg3(200, "keymanager response fieldname=%s value=\"%s\" pos=%d\n",
               fieldname, value, p-results.c_str());
         if (0==strcmp("error", fieldname)) {
            Mmsg(err_msg, "got error message: \"%s\"", value);
            break;
         } else if (0==strcmp("volume_name", fieldname)) {
            /* ignore */
         } else if (0==strcmp("comment", fieldname)) {
            comment=value;
            /* ignore */
         } else if (0==strcmp("cipher", fieldname)) {
            cipher_name = value;
            if (0==strcasecmp("AES_128_XTS", value)) {
               cipher = BLOCK_CIPHER_AES_128_XTS;
               cipher_key_size = 32;
            } else if (0==strcasecmp("AES_256_XTS", value)) {
               cipher = BLOCK_CIPHER_AES_256_XTS;
               cipher_key_size = 64;
            } else if (0==strcasecmp("NULL", value)) {
               cipher = BLOCK_CIPHER_NULL;
               cipher_key_size = 16;
            } else {
               cipher_key_size = 0;
               Mmsg(err_msg, "unknown cipher: \"%s\"", value);
               break;
            }
         } else if (0==strcmp("cipher_key", fieldname)) {
            in_cipher_key = value;
         } else if (0==strcmp("enc_cipher_key", fieldname)) {
            in_enc_cipher_key = value;
         } else if (0==strcmp("master_keyid", fieldname)) {
            in_master_keyid = value;
         }
      }
      if (err_msg.c_str()[0] == '\0') {
         /* no error, check that we have all the parameter we need */
         if (cipher == BLOCK_CIPHER_NONE) {
            Mmsg(err_msg, "cipher is missing");
         } else if (in_cipher_key == NULL) {
            Mmsg(err_msg, "key is missing");
         }
      }
      if (err_msg.c_str()[0] == '\0' && in_cipher_key != NULL) {
         /* no error, check that we can decode the key */
         in_cipher_key_size = base64_to_bin(keybuf, sizeof(keybuf), in_cipher_key, strlen(in_cipher_key));
         if (cipher_key_size != in_cipher_key_size) {
            Mmsg(err_msg, "Wrong cipher key size for \"%s\" expect %d, got %d", cipher_name, cipher_key_size, in_cipher_key_size);
         }
      }
      if (err_msg.c_str()[0] == '\0' && in_enc_cipher_key != NULL) {
         /* no error, check that we can decode the key */
         in_enc_cipher_key_size = base64_to_bin(enckeybuf, sizeof(enckeybuf), in_enc_cipher_key, strlen(in_enc_cipher_key));
      }
      if (err_msg.c_str()[0] == '\0' && in_master_keyid != NULL) {
         /* no error, check that we can decode the  master_keyid*/
         in_master_keyid_size = base64_to_bin(masterkeyidbuf, sizeof(masterkeyidbuf), in_master_keyid, strlen(in_master_keyid));
      }
   } else {
      /* status != 0 the script returned an error code */
      berrno be;
      be.set_errno(status);
      Mmsg(err_msg, "the key-manager returned an error see in key-manager log file, code=%d ERR=%s", status, be.bstrerror());
   }

   if (crypto_device_ctx != NULL) {
      block_cipher_context_free(crypto_device_ctx);
      crypto_device_ctx = NULL;
   }
   if (err_msg.c_str()[0] == '\0' && in_enc_cipher_key_size > 0) {
      if (in_enc_cipher_key_size > MAX_ENC_CIPHER_KEY_LEN) {
         Mmsg(err_msg, "encrypted key is too large");
      } else {
         *enc_cipher_key_size = in_enc_cipher_key_size;
         memcpy(enc_cipher_key, enckeybuf, *enc_cipher_key_size);
      }
   }
   if (err_msg.c_str()[0] == '\0' && in_master_keyid_size > 0) {
      if (in_master_keyid_size > MAX_MASTERKEY_ID_LEN) {
         Mmsg(err_msg, "masterkey id is too large");
      } else {
         *master_keyid_size = in_master_keyid_size;
         memcpy(master_keyid, masterkeyidbuf, *master_keyid_size);
      }
   }

   if (err_msg.c_str()[0] == '\0') {
      /* initialize the crypto context */
      crypto_device_ctx = block_cipher_context_new(cipher);
      block_cipher_init_key(crypto_device_ctx, (unsigned char*)keybuf);
      Jmsg(jcr, M_INFO, 0, _("3305 LoadEncryptionKey for Volume \"%s\", status is OK.\n"),
            dcr->VolumeName);
      if (comment!=NULL) {
         /* ignored for now */
      }
      Dmsg1(60, "load encryption key for volume %s OK\n", dcr->VolumeName);
   }
   if (err_msg.c_str()[0] != '\0') {
      Dmsg2(10, "load encryption key for volume %s Err=%s\n", dcr->VolumeName, err_msg.c_str());
#if 0
      Jmsg(jcr, M_FATAL, 0, _("3992 Bad LoadEncryptionKey %s Volume \"%s\": "
           "ERR=%s\n"), operation, dcr->VolumeName, err_msg.c_str());
#endif
      if (jcr != NULL) {
         Mmsg(jcr->errmsg, _("3992 Bad LoadEncryptionKey %s Volume \"%s\": "
               "ERR=%s\n"), operation, dcr->VolumeName, err_msg.c_str());
      }
      ok = false;
   } else {
      /* crypto key successfully loaded */
      if (op == op_label) {
      }
   }
   return ok;
}
