/*
   BAREOS® - Backup Archiving REcovery Open Sourced

   Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
   Copyright (C) 2011-2012 Planets Communications B.V.
   Copyright (C) 2013-2026 Bareos GmbH & Co. KG

   This program is Free Software; you can redistribute it and/or
   modify it under the terms of version three of the GNU Affero General Public
   License as published by the Free Software Foundation and included
   in the file LICENSE.

   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
   Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.
*/
// Kern Sibbald, December 2000
/**
 * @file
 * Subroutines to handle Catalog requests sent to the Director
 * Requests/commands from the Director are handled in dircmd.c
 */

#include "include/baconfig.h"
#include "include/bareos.h"
#include "stored/askdir.h"

#include "lib/parse_conf.h"
#include "stored/stored.h"
#include "stored/stored_globals.h"

#include "include/jcr.h"
#include "lib/crypto_cache.h"
#include "stored/device_control_record.h"
#include "stored/sd_device_control_record.h"
#include "stored/wait.h"
#include "stored/dev.h"
#include "lib/edit.h"
#include "lib/util.h"
#include "lib/berrno.h"
#include "lib/bsock.h"
#include "lib/serial.h"
#include <cinttypes>

namespace storagedaemon {

static const int debuglevel = 50;
static pthread_mutex_t vol_info_mutex = PTHREAD_MUTEX_INITIALIZER;

/* Requests sent to the Director */
inline constexpr const char Find_media[]
    = "CatReq Job=%s FindMedia=%d pool_name=%s media_type=%s "
      "unwanted_volumes=%s\n";
inline constexpr const char Get_Vol_Info[]
    = "CatReq Job=%s GetVolInfo VolName=%s write=%d\n";
inline constexpr const char Update_media[]
    = "CatReq Job=%s UpdateMedia VolName=%s"
      " VolJobs=%u VolFiles=%u VolBlocks=%u VolBytes=%s VolMounts=%u"
      " VolErrors=%u VolWrites=%u MaxVolBytes=%s EndTime=%s VolStatus=%s"
      " Slot=%d relabel=%d InChanger=%d VolReadTime=%s VolWriteTime=%s"
      " VolFirstWritten=%s\n";
inline constexpr const char Create_job_media[]
    = "CatReq Job=%s CreateJobMedia"
      " FirstIndex=%u LastIndex=%u StartFile=%u EndFile=%u"
      " StartBlock=%u EndBlock=%u Copy=%d Strip=%d MediaId=%s\n";
inline constexpr const char Update_jobrecord[]
    = "Catreq Job=%s UpdateJobRecord JobFiles=%" PRIu32 " JobBytes=%" PRIu64
      "\n";

inline constexpr const char Update_filelist[]
    = "Catreq Job=%s UpdateFileList\n";

inline constexpr const char FileAttributes[] = "UpdCat Job=%s FileAttributes ";


/* Responses received from the Director */
inline constexpr const char OK_media[]
    = "1000 OK VolName=%127s VolJobs=%u VolFiles=%lu"
      " VolBlocks=%lu VolBytes=%lld VolMounts=%lu VolErrors=%lu VolWrites=%lu"
      " MaxVolBytes=%lld VolCapacityBytes=%lld VolStatus=%20s"
      " Slot=%ld MaxVolJobs=%lu MaxVolFiles=%lu InChanger=%ld"
      " VolReadTime=%lld VolWriteTime=%lld EndFile=%lu EndBlock=%lu"
      " LabelType=%ld MediaId=%lld EncryptionKey=%127s"
      " MinBlocksize=%lu MaxBlocksize=%lu\n";
inline constexpr const char OK_create[] = "1000 OK CreateJobMedia\n";

/**
 * Common routine for:
 *   DirGetVolumeInfo()
 * and
 *   DirFindNextAppendableVolume()
 *
 *  NOTE!!! All calls to this routine must be protected by
 *          locking vol_info_mutex before calling it so that
 *          we don't have one thread modifying the parameters
 *          and another reading them.
 *
 *  Returns: true  on success and vol info in dcr->VolCatInfo
 *           false on failure
 */
static bool DoGetVolumeInfo(DeviceControlRecord* dcr)
{
  JobControlRecord* jcr = dcr->jcr;
  BareosSocket* dir = jcr->dir_bsock;
  int n;
  int32_t InChanger;

  dcr->setVolCatInfo(false);
  if (dir->recv() <= 0) {
    Dmsg0(debuglevel, "getvolname error BnetRecv\n");
    Mmsg(jcr->errmsg, T_("Network error on BnetRecv in req_vol_info.\n"));
    return false;
  }
  VolumeCatalogInfo vol;
  Dmsg1(debuglevel, "<dird %s", dir->msg);
  n = bsscanf(dir->msg, OK_media, vol.VolCatName, &vol.VolCatJobs,
              &vol.VolCatFiles, &vol.VolCatBlocks, &vol.VolCatBytes,
              &vol.VolCatMounts, &vol.VolCatErrors, &vol.VolCatWrites,
              &vol.VolCatMaxBytes, &vol.VolCatCapacityBytes, vol.VolCatStatus,
              &vol.Slot, &vol.VolCatMaxJobs, &vol.VolCatMaxFiles, &InChanger,
              &vol.VolReadTime, &vol.VolWriteTime, &vol.EndFile, &vol.EndBlock,
              &vol.LabelType, &vol.VolMediaId, vol.VolEncrKey,
              &vol.VolMinBlocksize, &vol.VolMaxBlocksize);
  if (n != 24) {
    Dmsg3(debuglevel, "Bad response from Dir fields=%d, len=%d: %s", n,
          dir->message_length, dir->msg);
    Mmsg(jcr->errmsg, T_("Error getting Volume info: %s"), dir->msg);
    return false;
  }
  vol.InChanger = InChanger; /* bool in structure */
  vol.is_valid = true;
  UnbashSpaces(vol.VolCatName);
  bstrncpy(dcr->VolumeName, vol.VolCatName, sizeof(dcr->VolumeName));
  dcr->VolCatInfo = vol; /* structure assignment */

  /* If we received a new crypto key update the cache and write out the new
   * cache on a change. */
  if (*vol.VolEncrKey) {
    if (UpdateCryptoCache(vol.VolCatName, vol.VolEncrKey)) {
      WriteCryptoCache(me->working_directory, "bareos-sd",
                       GetFirstPortHostOrder(me->SDaddrs));
    }
  }

  Dmsg4(debuglevel,
        "DoGetVolumeInfo return true slot=%d Volume=%s, "
        "VolminBlocksize=%u VolMaxBlocksize=%u\n",
        vol.Slot, vol.VolCatName, vol.VolMinBlocksize, vol.VolMaxBlocksize);
  Dmsg2(debuglevel,
        "setting dcr->VolMinBlocksize(%u) to vol.VolMinBlocksize(%u)\n",
        dcr->VolMinBlocksize, vol.VolMinBlocksize);
  Dmsg2(debuglevel,
        "setting dcr->VolMaxBlocksize(%u) to vol.VolMaxBlocksize(%u)\n",
        dcr->VolMaxBlocksize, vol.VolMaxBlocksize);

  // Assign the volcatinfo to the dcr.
  dcr->VolMinBlocksize = vol.VolMinBlocksize;
  dcr->VolMaxBlocksize = vol.VolMaxBlocksize;

  return true;
}

/**
 * Get Volume info for a specific volume from the Director's Database
 *
 * Returns: true  on success   (Director guarantees that Pool and MediaType
 *                              are correct and VolStatus==Append or
 *                              VolStatus==Recycle)
 *          false on failure
 *
 * Volume information returned in dcr->VolCatInfo
 */
bool StorageDaemonDeviceControlRecord::DirGetVolumeInfo(
    enum get_vol_info_rw writing)
{
  bool ok;
  BareosSocket* dir = jcr->dir_bsock;

  lock_mutex(vol_info_mutex);
  setVolCatName(VolumeName);
  BashSpaces(getVolCatName());
  dir->fsend(Get_Vol_Info, jcr->Job, getVolCatName(),
             (writing == GET_VOL_INFO_FOR_WRITE) ? 1 : 0);
  Dmsg1(debuglevel, ">dird %s", dir->msg);
  UnbashSpaces(getVolCatName());
  ok = DoGetVolumeInfo(this);
  unlock_mutex(vol_info_mutex);

  return ok;
}

/**
 * Get info on the next appendable volume in the Director's database
 *
 * Returns: true  on success dcr->VolumeName is volume
 *                reserve_volume() called on Volume name
 *          false on failure dcr->VolumeName[0] == 0
 *                also sets dcr->FoundInUse if at least one
 *                in use volume was found.
 *
 * Volume information returned in dcr
 */
bool StorageDaemonDeviceControlRecord::DirFindNextAppendableVolume()
{
  bool retval = false;
  BareosSocket* dir = jcr->dir_bsock;
  PoolMem unwanted_volumes(PM_MESSAGE);

  Dmsg2(debuglevel, "DirFindNextAppendableVolume: reserved=%d Vol=%s\n",
        IsReserved(), VolumeName);

  /* Try the twenty oldest or most available volumes. Note,
   * the most available could already be mounted on another
   * drive, so we continue looking for a not in use Volume. */
  with_volume_lock([&] {
    lock_mutex(vol_info_mutex);
    ClearFoundInUse();

    PmStrcpy(unwanted_volumes, "");

    std::size_t device_count = 0;

    {
      ResLocker _{my_config};

      BareosResource* found_dev = nullptr;
      foreach_res (found_dev, R_DEVICE) { device_count += 1; }
    }

    if (device_count == 0) {
      Emsg0(M_ERROR, 0,
            "Trying to find a volume, but there are apparently no devices.");
      // not sure what happened here, but this should hopefully be enough
      device_count = 100;
    }

    // x >> 3 == x / 8 ~ 10% * x
    // there should never be a need to ask for more volumes than there are
    // devices because each device should only be allowed to reserve one volume,
    // but we add some headroom here in case some recommended volume is broken
    // or in case some devices are currently reserving multiple volumes (maybe
    // they are currently switching volumes)
    std::size_t ask_limit = device_count + (device_count >> 3);

    Dmsg0(400, "device count = %llu => ask limit = %llu\n",
          static_cast<long long unsigned>(device_count),
          static_cast<long long unsigned>(ask_limit));

    // use <= because we start counting at 1
    for (std::size_t vol_index = 1; vol_index <= ask_limit; vol_index++) {
      BashSpaces(media_type);
      BashSpaces(pool_name);
      BashSpaces(unwanted_volumes.c_str());
      dir->fsend(Find_media, jcr->Job, static_cast<int>(vol_index), pool_name,
                 media_type, unwanted_volumes.c_str());
      UnbashSpaces(media_type);
      UnbashSpaces(pool_name);
      UnbashSpaces(unwanted_volumes.c_str());
      Dmsg1(debuglevel, ">dird %s", dir->msg);

      if (DoGetVolumeInfo(this)) {
        if (vol_index == 1) {
          PmStrcpy(unwanted_volumes, VolumeName);
        } else {
          PmStrcat(unwanted_volumes, ",");
          PmStrcat(unwanted_volumes, VolumeName);
        }

        if (Can_i_write_volume()) {
          Dmsg1(debuglevel, "Call reserve_volume for write. Vol=%s\n",
                VolumeName);
          if (reserve_volume(this, VolumeName) == NULL) {
            Dmsg2(debuglevel, "Could not reserve volume %s on %s\n", VolumeName,
                  dev->print_name());
            continue;
          }
          Dmsg1(debuglevel, "DirFindNextAppendableVolume return true. vol=%s\n",
                VolumeName);
          retval = true;
          goto get_out;
        } else {
          Dmsg1(debuglevel, "Volume %s is in use.\n", VolumeName);

          // If volume is not usable, it is in use by someone else
          SetFoundInUse();
          continue;
        }
      }
      Dmsg2(debuglevel, "No vol. index %zu return false. dev=%s\n", vol_index,
            dev->print_name());
      break;
    }
    VolumeName[0] = 0;

  get_out:
    unlock_mutex(vol_info_mutex);
  });

  return retval;
}

/**
 * After writing a Volume, send the updated statistics
 * back to the director. The information comes from the
 * dev record.
 */
bool StorageDaemonDeviceControlRecord::DirUpdateVolumeInfo(
    is_labeloperation label)
{
  BareosSocket* dir = jcr->dir_bsock;
  VolumeCatalogInfo* vol = &dev->VolCatInfo;
  char ed1[50], ed2[50], ed3[50], ed4[50], ed5[50], ed6[50];
  int InChanger;
  bool ok = false;
  PoolMem volume_name;

  // If system job, do not update catalog
  if (jcr->is_JobType(JT_SYSTEM)) { return true; }

  if (vol->VolCatName[0] == 0) {
    Jmsg0(jcr, M_FATAL, 0, T_("NULL Volume name. This shouldn't happen!!!\n"));
    Pmsg0(000, T_("NULL Volume name. This shouldn't happen!!!\n"));
    return false;
  }

  // Lock during Volume update
  lock_mutex(vol_info_mutex);
  Dmsg1(debuglevel, "Update cat VolBytes=%" PRIu64 "\n", vol->VolCatBytes);

  // Just labeled or relabeled the tape
  if (label == is_labeloperation::True) {
    bstrncpy(vol->VolCatStatus, "Append", sizeof(vol->VolCatStatus));
  }
  // Manually labeled volume was written on.
  // This should not occur because "update volume" > Volume Status does not list
  // "Unlabeled" as an option.
  else if (bstrncmp(vol->VolCatStatus, "Unlabeled", sizeof(vol->VolCatStatus))
           && vol->VolCatBytes > 0) {
    bstrncpy(vol->VolCatStatus, "Append", sizeof(vol->VolCatStatus));
  }
  vol->VolLastWritten = time(NULL);
  PmStrcpy(volume_name, vol->VolCatName);
  BashSpaces(volume_name);
  InChanger = vol->InChanger;
  dir->fsend(
      Update_media, jcr->Job, volume_name.c_str(), vol->VolCatJobs,
      vol->VolCatFiles, vol->VolCatBlocks, edit_uint64(vol->VolCatBytes, ed1),
      vol->VolCatMounts, vol->VolCatErrors, vol->VolCatWrites,
      edit_uint64(vol->VolCatMaxBytes, ed2),
      edit_uint64(vol->VolLastWritten, ed6), vol->VolCatStatus, vol->Slot,
      to_underlying(label), InChanger, /* bool in structure */
      edit_int64(vol->VolReadTime, ed3), edit_int64(vol->VolWriteTime, ed4),
      edit_uint64(vol->VolFirstWritten, ed5));
  Dmsg1(debuglevel, ">dird %s", dir->msg);

  // Do not lock device here because it may be locked from label
  if (!jcr->IsJobCanceled()) {
    if (!DoGetVolumeInfo(this)) {
      Jmsg(jcr, M_FATAL, 0, "%s", jcr->errmsg);
      Dmsg2(debuglevel, T_("Didn't get vol info vol=%s: ERR=%s\n"),
            vol->VolCatName, jcr->errmsg);
      goto bail_out;
    }
    Dmsg1(420, "get_volume_info() %s", dir->msg);

    // Update dev Volume info in case something changed (e.g. expired)
    dev->VolCatInfo = VolCatInfo;
    ok = true;
  }

bail_out:
  unlock_mutex(vol_info_mutex);
  return ok;
}

// After writing a Volume, create the JobMedia record.
bool StorageDaemonDeviceControlRecord::DirCreateJobmediaRecord(bool zero)
{
  BareosSocket* dir = jcr->dir_bsock;
  char ed1[50];

  // If system job, do not update catalog
  if (jcr->is_JobType(JT_SYSTEM)) { return true; }

  // Throw out records where FI is zero -- i.e. nothing done
  if (!zero && VolFirstIndex == 0 && (StartBlock != 0 || EndBlock != 0)) {
    Dmsg0(debuglevel, "JobMedia FI=0 StartBlock!=0 record suppressed\n");
    return true;
  }

  if (!WroteVol) { return true; /* nothing written to tape */ }

  WroteVol = false;
  if (zero) {
    // Send dummy place holder to avoid purging
    dir->fsend(Create_job_media, jcr->Job, 0u, 0u, 0u, 0u, 0u, 0u, 0, 0,
               edit_uint64(VolMediaId, ed1));
  } else {
    dir->fsend(Create_job_media, jcr->Job, VolFirstIndex, VolLastIndex,
               StartFile, EndFile, StartBlock, EndBlock, Copy, Stripe,
               edit_uint64(VolMediaId, ed1));
  }
  Dmsg1(debuglevel, ">dird %s", dir->msg);

  if (dir->recv() <= 0) {
    Dmsg0(debuglevel, "create_jobmedia error BnetRecv\n");
    Jmsg(jcr, M_FATAL, 0, T_("Error creating JobMedia record: ERR=%s\n"),
         dir->bstrerror());
    return false;
  }
  Dmsg1(debuglevel, "<dird %s", dir->msg);

  if (!bstrcmp(dir->msg, OK_create)) {
    Dmsg1(debuglevel, "Bad response from Dir: %s\n", dir->msg);
    Jmsg(jcr, M_FATAL, 0, T_("Error creating JobMedia record: %s\n"), dir->msg);
    return false;
  }

  return true;
}

/**
 * Update File Attribute data
 * We do the following:
 *  1. expand the bsock buffer to be large enough
 *  2. Write a "header" into the buffer with serialized data
 *     VolSessionId
 *     VolSeesionTime
 *     FileIndex
 *     Stream
 *     data length that follows
 *     start of raw byte data from the Device record.
 *
 * Note, this is primarily for Attribute data, but can
 * also handle any device record. The Director must know
 * the raw byte data format that is defined for each Stream.
 *
 * Now Restore Objects pass through here STREAM_RESTORE_OBJECT
 */
bool StorageDaemonDeviceControlRecord::DirUpdateFileAttributes(
    DeviceRecord* record)
{
  BareosSocket* dir = jcr->dir_bsock;
  ser_declare;

#ifdef NO_ATTRIBUTES_TEST
  return true;
#endif

  dir->msg = CheckPoolMemorySize(
      dir->msg, sizeof(FileAttributes) + MAX_NAME_LENGTH + sizeof(DeviceRecord)
                    + record->data_len + 1);
  dir->message_length
      = Bsnprintf(dir->msg, sizeof(FileAttributes) + MAX_NAME_LENGTH + 1,
                  FileAttributes, jcr->Job);
  SerBegin(dir->msg + dir->message_length, 0);
  ser_uint32(record->VolSessionId);
  ser_uint32(record->VolSessionTime);
  ser_int32(record->FileIndex);
  ser_int32(record->Stream);
  ser_uint32(record->data_len);
  SerBytes(record->data, record->data_len);
  dir->message_length = SerLength(dir->msg);
  Dmsg1(1800, ">dird %s", dir->msg); /* Attributes */

  return dir->send();
}

/**
 * Request the sysop to create an appendable volume
 *
 * Entered with device blocked.
 * Leaves with device blocked.
 *
 * Returns: true  on success (operator issues a mount command)
 *          false on failure
 *
 * Note, must create dev->errmsg on error return.
 *
 * On success, dcr->VolumeName and dcr->VolCatInfo contain
 * information on suggested volume, but this may not be the
 * same as what is actually mounted.
 *
 * When we return with success, the correct tape may or may not
 * actually be mounted. The calling routine must read it and
 * verify the label.
 */
bool StorageDaemonDeviceControlRecord::DirAskSysopToCreateAppendableVolume()
{
  int poll_mode = dev->poll;
  int status = W_TIMEOUT;
  bool got_vol = false;

  if (jcr->IsJobCanceled()) { return false; }

  Dmsg0(debuglevel, "enter DirAskSysopToCreateAppendableVolume\n");
  ASSERT(dev->blocked());
  for (;;) {
    if (jcr->IsJobCanceled()) {
      Mmsg(dev->errmsg,
           T_("Job %s canceled while waiting for mount on Storage Device "
              "\"%s\".\n"),
           jcr->Job, dev->print_name());
      Jmsg(jcr, M_INFO, 0, "%s", dev->errmsg);
      return false;
    }
    got_vol = DirFindNextAppendableVolume(); /* get suggested volume */
    if (got_vol) {
      goto get_out;
    } else {
      if (status == W_TIMEOUT || status == W_MOUNT) {
        Mmsg(dev->errmsg,
             T_("Job %s is waiting. Cannot find any appendable volumes.\n"
                "Please use the \"label\" command to create a new Volume for:\n"
                "    Storage:      %s\n"
                "    Pool:         %s\n"
                "    Media type:   %s\n"),
             jcr->Job, dev->print_name(), pool_name, media_type);
        Jmsg(jcr, M_MOUNT, 0, "%s", dev->errmsg);
        Dmsg1(debuglevel, "%s", dev->errmsg);
      }
    }

    jcr->sendJobStatus(JS_WaitMedia);

    status = WaitForSysop(this);
    Dmsg1(debuglevel, "Back from WaitForSysop status=%d\n", status);
    if (dev->poll) {
      Dmsg1(debuglevel, "Poll timeout in create append vol on device %s\n",
            dev->print_name());
      continue;
    }

    if (status == W_TIMEOUT) {
      if (!DoubleDevWaitTime(dev)) {
        Mmsg(dev->errmsg,
             T_("Max time exceeded waiting to mount Storage Device %s for Job "
                "%s\n"),
             dev->print_name(), jcr->Job);
        Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg);
        Dmsg1(debuglevel, "Gave up waiting on device %s\n", dev->print_name());
        return false; /* exceeded maximum waits */
      }
      continue;
    }

    if (status == W_ERROR) {
      BErrNo be;
      Mmsg0(dev->errmsg, T_("pthread error in mount_next_volume.\n"));
      Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg);
      return false;
    }
    Dmsg1(debuglevel, "Someone woke me for device %s\n", dev->print_name());
  }

get_out:
  dev->poll = poll_mode;  // we do not want to change the poll
                          // mode in this function
  jcr->sendJobStatus(JS_Running);
  Dmsg0(debuglevel, "leave dir_ask_sysop_to_mount_create_appendable_volume\n");

  return true;
}

/**
 * Request to mount specific Volume
 *
 * Entered with device blocked and dcr->VolumeName is desired volume.
 * Leaves with device blocked.
 *
 * Returns: true  on success (operator issues a mount command)
 *          false on failure
 *
 * Note, must create dev->errmsg on error return.
 */
bool StorageDaemonDeviceControlRecord::DirAskSysopToMountVolume(int mode)
{
  int status = W_TIMEOUT;

  Dmsg0(debuglevel, "enter DirAskSysopToMountVolume\n");
  if (!VolumeName[0]) {
    Mmsg0(dev->errmsg,
          T_("Cannot request another volume: no volume name given.\n"));
    return false;
  }
  ASSERT(dev->blocked());
  while (1) {
    if (jcr->IsJobCanceled()) {
      Mmsg(
          dev->errmsg,
          T_("Job %s canceled while waiting for mount on Storage Device %s.\n"),
          jcr->Job, dev->print_name());
      return false;
    }

    /* If we are not polling, and the wait timeout or the user explicitly did a
     * mount, send him the message. Otherwise skip it. */
    if (!dev->poll && (status == W_TIMEOUT || status == W_MOUNT)) {
      const char* msg;

      if (mode == ST_APPENDREADY) {
        msg = T_(
            "Please mount append Volume \"%s\" or label a new one for:\n"
            "    Job:          %s\n"
            "    Storage:      %s\n"
            "    Pool:         %s\n"
            "    Media type:   %s\n");
      } else {
        msg = T_(
            "Please mount read Volume \"%s\" for:\n"
            "    Job:          %s\n"
            "    Storage:      %s\n"
            "    Pool:         %s\n"
            "    Media type:   %s\n");
      }
      Jmsg(jcr, M_MOUNT, 0, msg, VolumeName, jcr->Job, dev->print_name(),
           pool_name, media_type);
      Dmsg3(debuglevel, "Mount \"%s\" on device \"%s\" for Job %s\n",
            VolumeName, dev->print_name(), jcr->Job);
    }

    jcr->sendJobStatus(JS_WaitMount);

    status = WaitForSysop(this); /* wait on device */
    Dmsg1(debuglevel, "Back from WaitForSysop status=%d\n", status);
    if (dev->poll) {
      Dmsg1(debuglevel, "Poll timeout in mount vol on device %s\n",
            dev->print_name());
      Dmsg1(debuglevel, "Blocked=%s\n", dev->print_blocked());
      goto get_out;
    }

    if (status == W_TIMEOUT) {
      if (!DoubleDevWaitTime(dev)) {
        Mmsg(dev->errmsg,
             T_("Max time exceeded waiting to mount Storage Device %s for Job "
                "%s\n"),
             dev->print_name(), jcr->Job);
        Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg);
        Dmsg1(debuglevel, "Gave up waiting on device %s\n", dev->print_name());
        return false; /* exceeded maximum waits */
      }
      continue;
    }

    if (status == W_ERROR) {
      BErrNo be;
      Mmsg(dev->errmsg, T_("pthread error in mount_volume\n"));
      Jmsg(jcr, M_FATAL, 0, "%s", dev->errmsg);
      return false;
    }
    Dmsg1(debuglevel, "Someone woke me for device %s\n", dev->print_name());
    break;
  }

get_out:
  jcr->sendJobStatus(JS_Running);
  Dmsg0(debuglevel, "leave DirAskSysopToMountVolume\n");
  return true;
}

bool StorageDaemonDeviceControlRecord::DirAskToUpdateFileList()
{
  BareosSocket* dir = jcr->dir_bsock;
  return dir->fsend(Update_filelist, jcr->Job);
}

bool StorageDaemonDeviceControlRecord::DirAskToUpdateJobRecord()
{
  BareosSocket* dir = jcr->dir_bsock;
  return dir->fsend(Update_jobrecord, jcr->Job, jcr->JobFiles, jcr->JobBytes);
}


// Dummy methods for everything but SD and BTAPE.
bool DeviceControlRecord::DirAskSysopToMountVolume(int /*mode*/)
{
  fprintf(stderr,
          T_("Mount Volume \"%s\" on device %s and press return when ready: "),
          VolumeName, dev->print_name());
  dev->close(this);
  getchar();
  return true;
}

bool DeleteNullJobmediaRecords(JobControlRecord* jcr)
{
  Dmsg0(100, "Deleting null jobmedia records\n");
  BareosSocket* dir = jcr->dir_bsock;
  const char* delete_null_records
      = "CatReq Job=%s DeleteNullJobmediaRecords jobid=%u";
  dir->fsend(delete_null_records, jcr->Job, jcr->JobId);
  if (dir->recv() <= 0) {
    Dmsg0(100, "DeleteNullJobmediaRecords error BnetRecv\n");
    Mmsg(jcr->errmsg,
         T_("Network error on BnetRecv in DeleteNullJobmediaRecords.\n"));
    return false;
  }
  Dmsg1(100, ">dird %s", dir->msg);
  if (strncmp(dir->msg, "1000", 4) == 0) { return true; }
  return false;
}

bool DeviceControlRecord::DirGetVolumeInfo(enum get_vol_info_rw)
{
  Dmsg0(100, "Fake DirGetVolumeInfo\n");
  setVolCatName(VolumeName);
  Dmsg1(500, "Vol=%s\n", getVolCatName());
  return 1;
}

DeviceControlRecord* DeviceControlRecord::get_new_spooling_dcr()
{
  return new StorageDaemonDeviceControlRecord;
}
} /* namespace storagedaemon */
