]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/stored_conf.c
Big backport from Enterprise
[bacula/bacula] / bacula / src / stored / stored_conf.c
index 99da6309abcb6a001b9a508fea0fff1047f2403f..43a47d9a2a24747b2b9d008083dffa9eb6eb440d 100644 (file)
@@ -1,7 +1,7 @@
 /*
    Bacula(R) - The Network Backup Solution
 
-   Copyright (C) 2000-2016 Kern Sibbald
+   Copyright (C) 2000-2017 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.
@@ -11,7 +11,7 @@
    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 
+   This notice must be preserved when any source code is
    conveyed and/or propagated.
 
    Bacula(R) is a registered trademark of Kern Sibbald.
 
 #include "bacula.h"
 #include "stored.h"
+#include "cloud_driver.h"
 
 /* First and last resource ids */
 int32_t r_first = R_FIRST;
 int32_t r_last  = R_LAST;
-static RES *sres_head[R_LAST - R_FIRST + 1];
-RES **res_head = sres_head;
+RES_HEAD **res_head;
 
 /* We build the current resource here statically,
  * then move it to dynamic memory */
@@ -78,6 +78,7 @@ static RES_ITEM store_items[] = {
    {"TlsAllowedCn",          store_alist_str, ITEM(res_store.tls_allowed_cns), 0, 0, 0},
    {"ClientConnectWait",     store_time,  ITEM(res_store.client_wait), 0, ITEM_DEFAULT, 30 * 60},
    {"VerId",                 store_str,   ITEM(res_store.verid), 0, 0, 0},
+   {"CommCompression",       store_bool,  ITEM(res_store.comm_compression), 0, ITEM_DEFAULT, true},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
@@ -108,6 +109,7 @@ static RES_ITEM dev_items[] = {
    {"MediaType",             store_strname,ITEM(res_dev.media_type),  0, ITEM_REQUIRED, 0},
    {"DeviceType",            store_devtype,ITEM(res_dev.dev_type),    0, 0, 0},
    {"ArchiveDevice",         store_strname,ITEM(res_dev.device_name), 0, ITEM_REQUIRED, 0},
+   {"AlignedDevice",         store_strname,ITEM(res_dev.adevice_name), 0, 0, 0},
    {"HardwareEndOfFile",     store_bit,  ITEM(res_dev.cap_bits), CAP_EOF,  ITEM_DEFAULT, 1},
    {"HardwareEndOfMedium",   store_bit,  ITEM(res_dev.cap_bits), CAP_EOM,  ITEM_DEFAULT, 1},
    {"BackwardSpaceRecord",   store_bit,  ITEM(res_dev.cap_bits), CAP_BSR,  ITEM_DEFAULT, 1},
@@ -137,6 +139,7 @@ static RES_ITEM dev_items[] = {
    {"ControlDevice",         store_strname,ITEM(res_dev.control_name), 0, 0, 0},
    {"ChangerCommand",        store_strname,ITEM(res_dev.changer_command), 0, 0, 0},
    {"AlertCommand",          store_strname,ITEM(res_dev.alert_command), 0, 0, 0},
+   {"LockCommand",           store_strname,ITEM(res_dev.lock_command), 0, 0, 0},
    {"MaximumChangerWait",    store_time,   ITEM(res_dev.max_changer_wait), 0, ITEM_DEFAULT, 5 * 60},
    {"MaximumOpenWait",       store_time,   ITEM(res_dev.max_open_wait), 0, ITEM_DEFAULT, 5 * 60},
    {"MaximumNetworkBufferSize", store_pint32, ITEM(res_dev.max_network_buffer_size), 0, 0, 0},
@@ -146,6 +149,7 @@ static RES_ITEM dev_items[] = {
    {"MaximumBlockSize",      store_maxblocksize, ITEM(res_dev.max_block_size), 0, 0, 0},
    {"PaddingSize",           store_size32, ITEM(res_dev.padding_size), 0, ITEM_DEFAULT, 4096},
    {"FileAlignment",         store_size32, ITEM(res_dev.file_alignment), 0, ITEM_DEFAULT, 4096},
+   {"MinimumAlignedSize",    store_size32, ITEM(res_dev.min_aligned_size), 0, ITEM_DEFAULT, 4096},
    {"MaximumVolumeSize",     store_size64, ITEM(res_dev.max_volume_size), 0, 0, 0},
    {"MaximumFileSize",       store_size64, ITEM(res_dev.max_file_size), 0, ITEM_DEFAULT, 1000000000},
    {"VolumeCapacity",        store_size64, ITEM(res_dev.volume_capacity), 0, 0, 0},
@@ -162,6 +166,7 @@ static RES_ITEM dev_items[] = {
    {"WritePartCommand",      store_strname,ITEM(res_dev.write_part_command), 0, 0, 0},
    {"FreeSpaceCommand",      store_strname,ITEM(res_dev.free_space_command), 0, 0, 0},
    {"LabelType",             store_label,  ITEM(res_dev.label_type), 0, 0, 0},
+   {"Cloud",                 store_res,    ITEM(res_dev.cloud), R_CLOUD, 0, 0},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
@@ -172,11 +177,30 @@ static RES_ITEM changer_items[] = {
    {"Device",            store_alist_res, ITEM(res_changer.device),   R_DEVICE, ITEM_REQUIRED, 0},
    {"ChangerDevice",     store_strname,   ITEM(res_changer.changer_name),    0, ITEM_REQUIRED, 0},
    {"ChangerCommand",    store_strname,   ITEM(res_changer.changer_command), 0, ITEM_REQUIRED, 0},
+   {"LockCommand",           store_strname,ITEM(res_changer.lock_command), 0, 0, 0},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
-
-// {"mountanonymousvolumes", store_bit,  ITEM(res_dev.cap_bits), CAP_ANONVOLS,   ITEM_DEFAULT, 0},
+/* Cloud driver definition */
+static RES_ITEM cloud_items[] = {
+   {"Name",              store_name,      ITEM(res_cloud.hdr.name),        0, ITEM_REQUIRED, 0},
+   {"Description",       store_str,       ITEM(res_cloud.hdr.desc),        0, 0, 0},
+   {"Driver",            store_cloud_driver, ITEM(res_cloud.driver_type), 0, ITEM_REQUIRED, 0},
+   {"HostName",          store_strname,ITEM(res_cloud.host_name), 0, ITEM_REQUIRED, 0},
+   {"BucketName",        store_strname,ITEM(res_cloud.bucket_name), 0, ITEM_REQUIRED, 0},
+   {"Region",            store_strname,ITEM(res_cloud.region), 0, 0, 0},
+   {"AccessKey",         store_strname,ITEM(res_cloud.access_key), 0, ITEM_REQUIRED, 0},
+   {"SecretKey",         store_strname,ITEM(res_cloud.secret_key), 0, ITEM_REQUIRED, 0},
+   {"Protocol",          store_protocol, ITEM(res_cloud.protocol), 0, ITEM_DEFAULT, 0},   /* HTTPS */
+   {"UriStyle",          store_uri_style, ITEM(res_cloud.uri_style), 0, ITEM_DEFAULT, 0}, /* VirtualHost */
+   {"TruncateCache",     store_truncate, ITEM(res_cloud.trunc_opt), 0, ITEM_DEFAULT, TRUNC_NO},
+   {"Upload",            store_upload,   ITEM(res_cloud.upload_opt), 0, ITEM_DEFAULT, UPLOAD_NO},
+   {"MaximumConcurrentUploads", store_pint32, ITEM(res_cloud.max_concurrent_uploads), 0, ITEM_DEFAULT, 0},
+   {"MaximumConcurrentDownloads", store_pint32, ITEM(res_cloud.max_concurrent_downloads), 0, ITEM_DEFAULT, 0},
+   {"MaximumUploadBandwidth", store_speed, ITEM(res_cloud.upload_limit), 0, 0, 0},
+   {"MaximumDownloadBandwidth", store_speed, ITEM(res_cloud.download_limit), 0, 0, 0},
+   {NULL, NULL, {0}, 0, 0, 0}
+};
 
 
 /* Message resource */
@@ -190,6 +214,7 @@ RES_TABLE resources[] = {
    {"Device",        dev_items,     R_DEVICE},
    {"Messages",      msgs_items,    R_MSGS},
    {"Autochanger",   changer_items, R_AUTOCHANGER},
+   {"Cloud",         cloud_items,   R_CLOUD},
    {NULL,            NULL,          0}
 };
 
@@ -201,38 +226,218 @@ RES_TABLE resources[] = {
 s_kw dev_types[] = {
    {"File",          B_FILE_DEV},
    {"Tape",          B_TAPE_DEV},
-   {"Dvd",           B_DVD_DEV},
    {"Fifo",          B_FIFO_DEV},
-   {"Vtl",           B_VTL_DEV},
    {"VTape",         B_VTAPE_DEV},
+   {"Vtl",           B_VTL_DEV},
+   {"Aligned",       B_ALIGNED_DEV},
+   {"Null",          B_NULL_DEV},
+   {"Cloud",         B_CLOUD_DEV},
    {NULL,            0}
 };
 
 
 /*
- * Store Device Type (File, FIFO, Tape, DVD)
+ * Store Device Type (File, FIFO, Tape, Cloud, ...)
  *
  */
 void store_devtype(LEX *lc, RES_ITEM *item, int index, int pass)
 {
-   int i;
+   bool found = false;
 
    lex_get_token(lc, T_NAME);
    /* Store the label pass 2 so that type is defined */
-   for (i=0; dev_types[i].name; i++) {
+   for (int i=0; dev_types[i].name; i++) {
       if (strcasecmp(lc->str, dev_types[i].name) == 0) {
          *(uint32_t *)(item->value) = dev_types[i].token;
-         i = 0;
+         found = true;
          break;
       }
    }
-   if (i != 0) {
+   if (!found) {
       scan_err1(lc, _("Expected a Device Type keyword, got: %s"), lc->str);
    }
    scan_to_eol(lc);
    set_bit(index, res_all.hdr.item_present);
 }
 
+/*
+ * Cloud drivers
+ *
+ *  driver     driver code
+ */
+s_kw cloud_drivers[] = {
+   {"S3",           C_S3_DRIVER},
+   {"File",         C_FILE_DRIVER},
+   {NULL,           0}
+};
+
+/*
+ * Store Device Type (File, FIFO, Tape, Cloud, ...)
+ *
+ */
+void store_cloud_driver(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+   bool found = false;
+
+   lex_get_token(lc, T_NAME);
+   /* Store the label pass 2 so that type is defined */
+   for (int i=0; cloud_drivers[i].name; i++) {
+      if (strcasecmp(lc->str, cloud_drivers[i].name) == 0) {
+         *(uint32_t *)(item->value) = cloud_drivers[i].token;
+         found = true;
+         break;
+      }
+   }
+   if (!found) {
+      scan_err1(lc, _("Expected a Cloud driver keyword, got: %s"), lc->str);
+   }
+   scan_to_eol(lc);
+   set_bit(index, res_all.hdr.item_present);
+}
+
+/*
+ * Cloud Truncate cache options
+ *
+ *   Option       option code = token
+ */
+s_kw trunc_opts[] = {
+   {"No",           TRUNC_NO},
+   {"AfterUpload",  TRUNC_AFTER_UPLOAD},
+   {"AtEndOfJob",   TRUNC_AT_ENDOFJOB},
+   {NULL,            0}
+};
+
+/*
+ * Store Cloud Truncate cache option (AfterUpload, AtEndOfJob, No)
+ *
+ */
+void store_truncate(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+   bool found = false;
+
+   lex_get_token(lc, T_NAME);
+   /* Store the label pass 2 so that type is defined */
+   for (int i=0; trunc_opts[i].name; i++) {
+      if (strcasecmp(lc->str, trunc_opts[i].name) == 0) {
+         *(uint32_t *)(item->value) = trunc_opts[i].token;
+         found = true;
+         break;
+      }
+   }
+   if (!found) {
+      scan_err1(lc, _("Expected a Truncate Cache option keyword, got: %s"), lc->str);
+   }
+   scan_to_eol(lc);
+   set_bit(index, res_all.hdr.item_present);
+}
+
+/*
+ * Cloud Upload options
+ *
+ *   Option         option code = token
+ */
+s_kw upload_opts[] = {
+   {"No",            UPLOAD_NO},
+   {"EachPart",      UPLOAD_EACHPART},
+   {"AtEndOfJob",    UPLOAD_AT_ENDOFJOB},
+   {NULL,            0}
+};
+
+/*
+ * Store Cloud Upload option (EachPart, AtEndOfJob, No)
+ *
+ */
+void store_upload(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+   bool found = false;
+
+   lex_get_token(lc, T_NAME);
+   /* Store the label pass 2 so that type is defined */
+   for (int i=0; upload_opts[i].name; i++) {
+      if (strcasecmp(lc->str, upload_opts[i].name) == 0) {
+         *(uint32_t *)(item->value) = upload_opts[i].token;
+         found = true;
+         break;
+      }
+   }
+   if (!found) {
+      scan_err1(lc, _("Expected a Cloud Upload option keyword, got: %s"), lc->str);
+   }
+   scan_to_eol(lc);
+   set_bit(index, res_all.hdr.item_present);
+}
+
+/*
+ * Cloud connection protocol  options
+ *
+ *   Option       option code = token
+ */
+s_kw proto_opts[] = {
+   {"HTTPS",        0},
+   {"HTTP",         1},
+   {NULL,            0}
+};
+
+/*
+ * Store Cloud connect protocol option (HTTPS, HTTP)
+ *
+ */
+void store_protocol(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+   bool found = false;
+
+   lex_get_token(lc, T_NAME);
+   /* Store the label pass 2 so that type is defined */
+   for (int i=0; proto_opts[i].name; i++) {
+      if (strcasecmp(lc->str, proto_opts[i].name) == 0) {
+         *(uint32_t *)(item->value) = proto_opts[i].token;
+         found = true;
+         break;
+      }
+   }
+   if (!found) {
+      scan_err1(lc, _("Expected a Cloud communications protocol option keyword, got: %s"), lc->str);
+   }
+   scan_to_eol(lc);
+   set_bit(index, res_all.hdr.item_present);
+}
+
+/*
+ * Cloud Uri Style options
+ *
+ *   Option       option code = token
+ */
+s_kw uri_opts[] = {
+   {"VirtualHost",  0},
+   {"Path",         1},
+   {NULL,            0}
+};
+
+/*
+ * Store Cloud Uri Style option
+ *
+ */
+void store_uri_style(LEX *lc, RES_ITEM *item, int index, int pass)
+{
+   bool found = false;
+
+   lex_get_token(lc, T_NAME);
+   /* Store the label pass 2 so that type is defined */
+   for (int i=0; uri_opts[i].name; i++) {
+      if (strcasecmp(lc->str, uri_opts[i].name) == 0) {
+         *(uint32_t *)(item->value) = uri_opts[i].token;
+         found = true;
+         break;
+      }
+   }
+   if (!found) {
+      scan_err1(lc, _("Expected a Cloud Uri Style option keyword, got: %s"), lc->str);
+   }
+   scan_to_eol(lc);
+   set_bit(index, res_all.hdr.item_present);
+}
+
+
 /*
  * Store Maximum Block Size, and check it is not greater than MAX_BLOCK_LENGTH
  *
@@ -354,7 +559,24 @@ void dump_resource(int type, RES *rres, void sendit(void *sock, const char *fmt,
          bstrncat(buf, "CAP_OFFLINEUNMOUNT ", sizeof(buf));
       }
       bstrncat(buf, "\n", sizeof(buf));
-      sendit(sock, buf);
+      sendit(sock, buf);  /* Send caps string */
+      if (res->res_dev.cloud) {
+         sendit(sock, "   --->Cloud: name=%s\n", res->res_dev.cloud->hdr.name);
+      }
+      break;
+   case R_CLOUD:
+      sendit(sock, "Cloud: name=%s Driver=%d\n"
+         "      HostName=%s\n"
+         "      BucketName=%s\n"
+         "      AccessKey=%s SecretKey=%s\n"
+         "      AuthRegion=%s\n"
+         "      Protocol=%d UriStyle=%d\n",
+         res->res_cloud.hdr.name, res->res_cloud.driver_type,
+         res->res_cloud.host_name,
+         res->res_cloud.bucket_name,
+         res->res_cloud.access_key, res->res_cloud.secret_key,
+         res->res_cloud.region,
+         res->res_cloud.protocol, res->res_cloud.uri_style);
       break;
    case R_AUTOCHANGER:
       DEVRES *dev;
@@ -364,8 +586,6 @@ void dump_resource(int type, RES *rres, void sendit(void *sock, const char *fmt,
       foreach_alist(dev, res->res_changer.device) {
          sendit(sock, "   --->Device: name=%s\n", dev->hdr.name);
       }
-      bstrncat(buf, "\n", sizeof(buf));
-      sendit(sock, buf);
       break;
    case R_MSGS:
       sendit(sock, "Messages: name=%s\n", res->res_msgs.hdr.name);
@@ -378,9 +598,9 @@ void dump_resource(int type, RES *rres, void sendit(void *sock, const char *fmt,
       sendit(sock, _("Warning: unknown resource type %d\n"), type);
       break;
    }
-   if (recurse && res->res_dir.hdr.next) {
-      dump_resource(type, (RES *)res->res_dir.hdr.next, sendit, sock);
-   }
+   rres = GetNextRes(type, rres);
+   if (recurse && rres)
+      dump_resource(type, rres, sendit, sock);
 }
 
 /*
@@ -392,14 +612,12 @@ void dump_resource(int type, RES *rres, void sendit(void *sock, const char *fmt,
  */
 void free_resource(RES *sres, int type)
 {
-   RES *nres;
    URES *res = (URES *)sres;
 
    if (res == NULL)
       return;
 
    /* common stuff -- free the resource name */
-   nres = (RES *)res->res_dir.hdr.next;
    if (res->res_dir.hdr.name) {
       free(res->res_dir.hdr.name);
    }
@@ -445,6 +663,9 @@ void free_resource(RES *sres, int type)
       if (res->res_changer.changer_command) {
          free(res->res_changer.changer_command);
       }
+      if (res->res_changer.lock_command) {
+         free(res->res_changer.lock_command);
+      }
       if (res->res_changer.device) {
          delete res->res_changer.device;
       }
@@ -497,6 +718,23 @@ void free_resource(RES *sres, int type)
          free(res->res_store.verid);
       }
       break;
+   case R_CLOUD:
+      if (res->res_cloud.host_name) {
+         free(res->res_cloud.host_name);
+      }
+      if (res->res_cloud.bucket_name) {
+         free(res->res_cloud.bucket_name);
+      }
+      if (res->res_cloud.access_key) {
+         free(res->res_cloud.access_key);
+      }
+      if (res->res_cloud.secret_key) {
+         free(res->res_cloud.secret_key);
+      }
+      if (res->res_cloud.region) {
+         free(res->res_cloud.region);
+      }
+      break;
    case R_DEVICE:
       if (res->res_dev.media_type) {
          free(res->res_dev.media_type);
@@ -504,6 +742,9 @@ void free_resource(RES *sres, int type)
       if (res->res_dev.device_name) {
          free(res->res_dev.device_name);
       }
+      if (res->res_dev.adevice_name) {
+         free(res->res_dev.adevice_name);
+      }
       if (res->res_dev.control_name) {
          free(res->res_dev.control_name);
       }
@@ -516,6 +757,9 @@ void free_resource(RES *sres, int type)
       if (res->res_dev.alert_command) {
          free(res->res_dev.alert_command);
       }
+      if (res->res_dev.lock_command) {
+         free(res->res_dev.lock_command);
+      }
       if (res->res_dev.spool_directory) {
          free(res->res_dev.spool_directory);
       }
@@ -553,16 +797,13 @@ void free_resource(RES *sres, int type)
    if (res) {
       free(res);
    }
-   if (nres) {
-      free_resource(nres, type);
-   }
 }
 
 /* Save the new resource by chaining it into the head list for
  * the resource. If this is pass 2, we update any resource
  * or alist pointers.
  */
-void save_resource(int type, RES_ITEM *items, int pass)
+bool save_resource(CONFIG *config, int type, RES_ITEM *items, int pass)
 {
    URES *res;
    int rindex = type - r_first;
@@ -575,13 +816,15 @@ void save_resource(int type, RES_ITEM *items, int pass)
    for (i=0; items[i].name; i++) {
       if (items[i].flags & ITEM_REQUIRED) {
          if (!bit_is_set(i, res_all.res_dir.hdr.item_present)) {
-            Emsg2(M_ERROR_TERM, 0, _("\"%s\" directive is required in \"%s\" resource, but not found.\n"),
-              items[i].name, resources[rindex].name);
-          }
+            Mmsg(config->m_errmsg, _("\"%s\" directive is required in \"%s\" resource, but not found.\n"),
+                 items[i].name, resources[rindex].name);
+            return false;
+         }
       }
       /* If this triggers, take a look at lib/parse_conf.h */
       if (i >= MAX_RES_ITEMS) {
-         Emsg1(M_ERROR_TERM, 0, _("Too many directives in \"%s\" resource\n"), resources[rindex].name);
+         Mmsg(config->m_errmsg, _("Too many directives in \"%s\" resource\n"), resources[rindex].name);
+         return false;
       }
    }
 
@@ -595,28 +838,31 @@ void save_resource(int type, RES_ITEM *items, int pass)
       int errstat;
       switch (type) {
       /* Resources not containing a resource */
-      case R_DEVICE:
       case R_MSGS:
+      case R_CLOUD:
          break;
 
       /* Resources containing a resource or an alist */
       case R_DIRECTOR:
          if ((res = (URES *)GetResWithName(R_DIRECTOR, res_all.res_dir.hdr.name)) == NULL) {
-            Emsg1(M_ERROR_TERM, 0, _("Cannot find Director resource %s\n"), res_all.res_dir.hdr.name);
+            Mmsg(config->m_errmsg, _("Cannot find Director resource %s\n"), res_all.res_dir.hdr.name);
+            return false;
          }
          res->res_dir.tls_allowed_cns = res_all.res_dir.tls_allowed_cns;
          break;
       case R_STORAGE:
          if ((res = (URES *)GetResWithName(R_STORAGE, res_all.res_dir.hdr.name)) == NULL) {
-            Emsg1(M_ERROR_TERM, 0, _("Cannot find Storage resource %s\n"), res_all.res_dir.hdr.name);
+            Mmsg(config->m_errmsg,  _("Cannot find Storage resource %s\n"), res_all.res_dir.hdr.name);
+            return false;
          }
          res->res_store.messages = res_all.res_store.messages;
          res->res_store.tls_allowed_cns = res_all.res_store.tls_allowed_cns;
          break;
       case R_AUTOCHANGER:
          if ((res = (URES *)GetResWithName(type, res_all.res_changer.hdr.name)) == NULL) {
-            Emsg1(M_ERROR_TERM, 0, _("Cannot find AutoChanger resource %s\n"),
-                  res_all.res_changer.hdr.name);
+            Mmsg(config->m_errmsg, _("Cannot find AutoChanger resource %s\n"),
+                 res_all.res_changer.hdr.name);
+            return false;
          }
          /* we must explicitly copy the device alist pointer */
          res->res_changer.device   = res_all.res_changer.device;
@@ -627,13 +873,19 @@ void save_resource(int type, RES_ITEM *items, int pass)
          foreach_alist(dev, res->res_changer.device) {
             dev->changer_res = (AUTOCHANGER *)&res->res_changer;
          }
-         if ((errstat = rwl_init(&res->res_changer.changer_lock,
-                                 PRIO_SD_ACH_ACCESS)) != 0)
-         {
+         if ((errstat = rwl_init(&res->res_changer.changer_lock, PRIO_SD_ACH_ACCESS)) != 0) {
             berrno be;
-            Jmsg1(NULL, M_ERROR_TERM, 0, _("Unable to init lock: ERR=%s\n"),
-                  be.bstrerror(errstat));
+            Mmsg(config->m_errmsg, _("Unable to init lock for Autochanger=%s: ERR=%s\n"),
+                 res_all.res_changer.hdr.name, be.bstrerror(errstat));
+            return false;
+         }
+         break;
+      case R_DEVICE:
+         if ((res = (URES *)GetResWithName(R_DEVICE, res_all.res_dev.hdr.name)) == NULL) {
+            Mmsg(config->m_errmsg,  _("Cannot find Device resource %s\n"), res_all.res_dir.hdr.name);
+            return false;
          }
+         res->res_dev.cloud = res_all.res_dev.cloud;
          break;
       default:
          printf(_("Unknown resource type %d\n"), type);
@@ -650,7 +902,7 @@ void save_resource(int type, RES_ITEM *items, int pass)
          free(res_all.res_dir.hdr.desc);
          res_all.res_dir.hdr.desc = NULL;
       }
-      return;
+      return true;
    }
 
    /* The following code is only executed on pass 1 */
@@ -670,6 +922,9 @@ void save_resource(int type, RES_ITEM *items, int pass)
       case R_AUTOCHANGER:
          size = sizeof(AUTOCHANGER);
          break;
+      case R_CLOUD:
+         size = sizeof(CLOUD);
+         break;
       default:
          printf(_("Unknown resource type %d\n"), type);
          error = 1;
@@ -678,31 +933,16 @@ void save_resource(int type, RES_ITEM *items, int pass)
    }
    /* Common */
    if (!error) {
-      res = (URES *)malloc(size);
-      memcpy(res, &res_all, size);
-      if (!res_head[rindex]) {
-         res_head[rindex] = (RES *)res; /* store first entry */
-      } else {
-         RES *next, *last;
-         /* Add new res to end of chain */
-         for (last=next=res_head[rindex]; next; next=next->next) {
-            last = next;
-            if (strcmp(next->name, res->res_dir.hdr.name) == 0) {
-               Emsg2(M_ERROR_TERM, 0,
-                  _("Attempt to define second \"%s\" resource named \"%s\" is not permitted.\n"),
-                  resources[rindex].name, res->res_dir.hdr.name);
-            }
-         }
-         last->next = (RES *)res;
-         Dmsg2(90, "Inserting %s res: %s\n", res_to_str(type),
-               res->res_dir.hdr.name);
+      if (!config->insert_res(rindex, size)) {
+         return false;
       }
    }
+   return true;
 }
 
 bool parse_sd_config(CONFIG *config, const char *configfile, int exit_code)
 {
    config->init(configfile, NULL, exit_code, (void *)&res_all, res_all_size,
-      r_first, r_last, resources, res_head);
+      r_first, r_last, resources, &res_head);
    return config->parse_config();
 }