]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add support for the APPEND and IMMUTABLE flags for File Volumes
authorMichal Rakowski <michal.rakowski@baculasystems.com>
Thu, 2 Sep 2021 14:32:52 +0000 (16:32 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 14 Sep 2023 11:56:56 +0000 (13:56 +0200)
bacula/platforms/systemd/bacula-sd.service.in
bacula/src/stored/block_util.c
bacula/src/stored/dev.h
bacula/src/stored/dircmd.c
bacula/src/stored/file_dev.c
bacula/src/stored/file_dev.h
bacula/src/stored/mount.c
bacula/src/stored/stored_conf.c
bacula/src/stored/stored_conf.h

index 0e7b50eb94b9b3cc365c1f0e0bc8ad32ad02f223..c3637a22f01b8328cdf8430c1fddc3ac2efd7a8a 100644 (file)
@@ -32,6 +32,10 @@ ExecStart=@sbindir@/bacula-sd -dt -c @sysconfdir@/bacula-sd.conf
 StandardError=syslog
 TimeoutStopSec=3min
 LimitMEMLOCK=infinity
+LimitNOFILE=10000
+LimitNPROC=10000
+OOMScoreAdjust=-1000
+AmbientCapabilities=CAP_LINUX_IMMUTABLE
 
 [Install]
 WantedBy=multi-user.target
index 1fde128cdd8203b96651e4753429a7a31f11f504..b770b193d4e53d10078572e43a1b25363025e06c 100644 (file)
@@ -619,6 +619,16 @@ bool is_user_volume_size_reached(DCR *dcr, bool quiet)
             edit_uint64_with_commas(max_size, ed1),  dev->print_name(),
             dev->getVolCatName());
       }
+
+      if (dev->device->protect_vols) {
+         /* Set volume as immutable */
+         if (!dev->set_immutable(dev->getVolCatName())) {
+            /* We may proceed with that but warn the user */
+            Jmsg(dcr->jcr, M_WARNING, 0, _("Failed to set the volume %s on device %s as immutable, ERR=%s.\n"),
+                 dev->getVolCatName(), dev->print_name(), dev->errmsg);
+         }
+      }
+
       Dmsg4(100, "Maximum volume size %s exceeded Vol=%s device=%s.\n"
          "Marking Volume \"%s\" as Full.\n",
          edit_uint64_with_commas(max_size, ed1), dev->getVolCatName(),
index 06ca67e38478072b61ee304c3b16c6bb72d186d7..95017fabb0b8de2ddb720662f2abb4ef3a377462 100644 (file)
@@ -596,6 +596,14 @@ public:
    virtual bool end_of_job(DCR *dcr, uint32_t truncate) {return true; };
    virtual bool is_indexed() { return true; };
    virtual void set_ateof();                    /* in dev.c */
+   /* Methods below are responsible for managing
+    * the append and immutable flags on device-specific volumes */
+   virtual bool set_append_only(const char *vol_name) { return true; };
+   virtual bool clear_append_only(const char *vol_name) { return true; };
+   virtual bool set_immutable(const char *vol_name) { return true; };
+   virtual bool clear_immutable(const char *vol_name) { return true; };
+   virtual bool check_volume_protection_time(const char *vol_name) { return true; };
+   virtual bool check_for_immutable(const char *vol_name) { return true; };
    virtual const char *print_type() = 0;        /* in dev.c */
    virtual const char *print_driver_type() { return "";};
    virtual const char *print_full_type() { return print_type();};
index 6bb2a89d2a0b78b7f891a30b45b425386c1fdba5..3838cc65d2d791e78a34626a548b2d51115ccd49 100644 (file)
@@ -1004,6 +1004,7 @@ static void label_volume_if_ok(DCR *dcr, char *oldname,
    int mode;
    const char *volname = (relabel == 1) ? oldname : newname;
    uint64_t volCatBytes;
+   bool opened = false;
 
    if (!obtain_device_block(dev,
                             &hold,
@@ -1038,12 +1039,33 @@ static void label_volume_if_ok(DCR *dcr, char *oldname,
    } else {
       mode = CREATE_READ_WRITE;
    }
-   if (!dev->open_device(dcr, mode)) {
-      dir->fsend(_("3929 Unable to open device \"%s\": ERR=%s\n"),
-         dev->print_name(), dev->bstrerror());
-      goto bail_out;
+
+   if (dev->check_volume_protection_time(volname)) {
+      if (!dev->open_device(dcr, mode)) {
+         /* Open with 'READ_WRITE' fails if immutable flag is set, check if that's the case */
+         if (dev->check_for_immutable(volname)) {
+            /* Volume has immutable flag set, we need to clear it */
+            if (dev->clear_immutable(volname)) {
+               /* It should be now possible to open the device with desired mode */
+               if (dev->open_device(dcr, mode)) {
+                  opened = true;
+               }
+            } else {
+               dir->fsend(_("3929 Unable to clear immutable flag for device: \"%s\". ERR=%s\n"),
+                     dev->bstrerror());
+            }
+         }
+      } else {
+         opened = true;
+      }
    }
 
+      if (!opened) {
+         dir->fsend(_("3929 Unable to open device \"%s\": ERR=%s\n"),
+               dev->print_name(), dev->bstrerror());
+         goto bail_out;
+      }
+
    /* See what we have for a Volume */
    label_status = dev->read_dev_volume_label(dcr);
 
index 2d8dce27ea3f9aca54a19e6f5320cbc9a1a11ff4..d547bbdcf74f33154e2116a2cde3780f89df56e7 100644 (file)
 #include "bacula.h"
 #include "stored.h"
 
+#ifdef HAVE_LINUX_OS
+#include <linux/fs.h>
+#endif
+
 static const int dbglvl = 100;
 
 /* Imported functions */
@@ -179,11 +183,15 @@ bool file_dev::open_device(DCR *dcr, int omode)
    mount(1);                          /* do mount if required */
 
    set_mode(omode);
+
+   /* Check if volume needs to be opened with O_APPEND */
+   int append = append_open_needed(getVolCatName()) ? O_APPEND : 0;
+
    /* If creating file, give 0640 permissions */
    Dmsg3(100, "open disk: mode=%s open(%s, 0x%x, 0640)\n", mode_to_str(omode),
          archive_name.c_str(), mode);
    /* Use system open() */
-   if ((m_fd = ::open(archive_name.c_str(), mode|O_CLOEXEC, 0640)) < 0) {
+   if ((m_fd = ::open(archive_name.c_str(), mode|O_CLOEXEC|append, 0640)) < 0) {
       berrno be;
       dev_errno = errno;
       Mmsg3(errmsg, _("Could not open(%s,%s,0640): ERR=%s\n"),
@@ -236,6 +244,14 @@ bool DEVICE::truncate(DCR *dcr)
    }
 
    Dmsg2(100, "Truncate adata=%d fd=%d\n", dev->adata, dev->m_fd);
+
+   /* Need to clear the APPEND flag before truncating */
+   if (!clear_append_only(dcr->VolumeName)) {
+      Mmsg2(errmsg, _("Unable to clear append_only flag for volume %s on device %s.\n"),
+            dcr->VolumeName, print_name());
+      return false;
+   }
+
    if (ftruncate(dev->m_fd, 0) != 0) {
       berrno be;
       Mmsg2(errmsg, _("Unable to truncate device %s. ERR=%s\n"),
@@ -488,6 +504,267 @@ bool file_dev::is_eod_valid(DCR *dcr)
    return true;
 }
 
+/* Check if attribute is supported for current platform */
+bool file_dev::is_attribute_supported(int attr)
+{
+   int supported = false;
+
+   switch (attr) {
+#ifdef HAVE_APPEND_FL
+      case FS_APPEND_FL:
+         supported = true;
+         break;
+#endif // HAVE_APPEND_FL
+#ifdef HAVE_IMMUTABLE_FL
+      case FS_IMMUTABLE_FL:
+         supported = true;
+         break;
+#endif // HAVE_IMMUTABLE_FL
+      default:
+         break;
+   }
+
+   return supported;
+}
+
+/* Get full volume path (archive dir + volume name) */
+void file_dev::get_volume_fpath(const char *vol_name, POOLMEM **fname)
+{
+   pm_strcpy(fname, dev_name);
+   if (!IsPathSeparator((*fname)[strlen(*fname)-1])) {
+      pm_strcat(fname, "/");
+   }
+
+   pm_strcat(fname, vol_name);
+
+   if (is_adata()) {
+      pm_strcat(fname, ADATA_EXTENSION);
+   }
+}
+
+/* Check if volume can be reused or not yet.
+ * Used in the truncate path.
+ * This method is based on the 'MinimumVolumeProtection' time directive,
+ * current system time is compared against m_time of volume file.
+ *
+ * @return true  if volume can be reused
+ * @return false if volume's protection time hasn't expired yet,
+ *               hence volume cannot be reused now
+ */
+bool file_dev::check_volume_protection_time(const char *vol_name)
+{
+   if (!device->protect_vols) {
+      return true;
+   }
+
+   struct stat sp;
+   POOL_MEM fname(PM_FNAME);
+
+   if (device->min_volume_protection_time == 0) {
+      Mmsg(errmsg, _("Immutable flag cannot be cleared for volume: %s, "
+                    "because Minimum Volume Protection Time is set to 0\n"),
+                    vol_name);
+      return false;
+   }
+
+   get_volume_fpath(vol_name, fname.handle());
+
+   if (stat(fname.c_str(), &sp)) {
+      berrno be;
+      Mmsg2(errmsg, "Failed to stat %s, ERR=%s", fname.c_str(), be.bstrerror());
+      return false;
+   }
+
+   /* Check if enough time elapsed since last file's modification and compare it with current */
+   time_t expiration_time = sp.st_mtime + device->min_volume_protection_time;
+   time_t now = time(NULL);
+   char dt[50], dt2[50];
+   bstrftime(dt, sizeof(dt), expiration_time);
+   bstrftime(dt2, sizeof(dt2), now);
+   if (expiration_time > now) {
+      Mmsg1(errmsg, _("Immutable flag cannot be cleared for volume: %s, "
+                      "because Minimum Volume Protection Time hasn't expired yet.\n"),
+            vol_name);
+      Dmsg3(dbglvl, "Immutable flag cannot be cleared for volume: %s, "
+                    "because:\nexpiration time: %s\nnow: %s\n",
+                    vol_name, dt, dt2);
+      return false;
+   }
+
+   return true;
+}
+
+bool file_dev::check_for_attr(const char *vol_name, int attr)
+{
+   int tmp_fd, ioctl_ret, get_attr;
+   bool ret = false;
+   POOL_MEM fname(PM_FNAME);
+
+   if (!is_attribute_supported(attr)) {
+      Mmsg1(errmsg, "File attribute 0x%0x is not supported\n", attr);
+      return ret;
+   }
+
+   get_volume_fpath(vol_name, fname.handle());
+
+   if ((tmp_fd = d_open(fname.c_str(), O_RDONLY|O_CLOEXEC)) < 0) {
+      berrno be;
+      Mmsg2(errmsg, "Failed to open %s, ERR=%s", fname.c_str(), be.bstrerror());
+      return ret;
+   }
+
+   ioctl_ret = d_ioctl(tmp_fd, FS_IOC_GETFLAGS, (char *)&get_attr);
+   if (ioctl_ret < 0) {
+      berrno be;
+      Mmsg2(errmsg, "Failed to get attributes for %s, ERR=%s", fname.c_str(), be.bstrerror());
+   } else {
+      ret = get_attr & attr;
+      const char *msg_str = ret ? "set" : "not set";
+      Dmsg3(dbglvl, "Attribute: 0x%08x is %s for volume: %s\n",
+            attr, msg_str, fname.c_str());
+   }
+
+   d_close(tmp_fd);
+
+   return ret;
+}
+
+bool file_dev::modify_fattr(const char *vol_name, int attr, bool set)
+{
+   bool ret = false;
+   int tmp_fd, ioctl_ret, get_attr, set_attr;
+   const char *msg_str = set ? "set" : "cleared";
+   POOL_MEM fname(PM_FNAME);
+
+   if (!got_caps_needed) {
+      return true; /* We cannot set needed attributes, no work here */
+   }
+
+   if (!is_attribute_supported(attr)) {
+      Mmsg1(errmsg, "File attribute 0x%0x is not supported\n", attr);
+      return ret;
+   }
+
+   get_volume_fpath(vol_name, fname.handle());
+
+   if ((tmp_fd = d_open(fname.c_str(), O_RDONLY|O_CLOEXEC)) < 0) {
+      berrno be;
+      Mmsg2(errmsg, "Failed to open %s, ERR=%s", fname.c_str(), be.bstrerror());
+      return false;
+   }
+
+   ioctl_ret = d_ioctl(tmp_fd, FS_IOC_GETFLAGS, (char *)&get_attr);
+   if (ioctl_ret < 0) {
+      berrno be;
+      Mmsg2(errmsg, "Failed to get attributes for %s, ERR=%s", fname.c_str(), be.bstrerror());
+      goto bail_out;
+   }
+
+   if (set) {
+      /* Add new attribute to the currently set ones */
+      set_attr = get_attr | attr;
+   } else {
+      /* Inverse the desired attribute and later and it with the current state
+       * so that we clear only desired flag and do not touch all the rest */
+      int rev_mask = ~attr;
+      set_attr = get_attr & rev_mask;
+   }
+
+   ioctl_ret = d_ioctl(tmp_fd, FS_IOC_SETFLAGS, (char *)&set_attr);
+   if (ioctl_ret < 0) {
+      berrno be;
+      if (set) {
+         Mmsg3(errmsg, "Failed to set 0x%0x attribute for %s, err: %d\n", attr, fname.c_str(), errno);
+      } else {
+         Mmsg3(errmsg, "Failed to clear 0x%0x attribute for %s, err: %d\n", attr, fname.c_str(), errno);
+      }
+      goto bail_out;
+   }
+
+   Dmsg3(dbglvl, "Attribute: 0x%08x was %s for volume: %s\n",
+         attr, msg_str, fname.c_str());
+
+   ret = true;
+
+bail_out:
+   if (tmp_fd >= 0) {
+      d_close(tmp_fd);
+   }
+   return ret;
+}
+
+bool file_dev::set_fattr(const char *vol_name, int attr)
+{
+   return modify_fattr(vol_name, attr, true);
+}
+
+bool file_dev::clear_fattr(const char *vol_name, int attr)
+{
+   return modify_fattr(vol_name, attr, false);
+}
+
+#ifdef HAVE_APPEND_FL
+bool file_dev::append_open_needed(const char *vol_name)
+{
+   return check_for_attr(vol_name, FS_APPEND_FL);
+}
+
+bool file_dev::set_append_only(const char *vol_name)
+{
+   return set_fattr(vol_name, FS_APPEND_FL);
+}
+
+bool file_dev::clear_append_only(const char *vol_name)
+{
+   return clear_fattr(vol_name, FS_APPEND_FL);
+}
+#else
+bool file_dev::append_open_needed(const char *vol_name)
+{
+   return false;
+}
+bool file_dev::set_append_only(const char *vol_name)
+{
+   return true;
+}
+
+bool file_dev::clear_append_only(const char *vol_name)
+{
+   return true;
+}
+#endif // HAVE_APPEND_FL
+
+#ifdef HAVE_IMMUTABLE_FL
+bool file_dev::set_immutable(const char *vol_name)
+{
+   return set_fattr(vol_name, FS_IMMUTABLE_FL);
+}
+
+bool file_dev::clear_immutable(const char *vol_name)
+{
+   return clear_fattr(vol_name, FS_IMMUTABLE_FL);
+}
+
+bool file_dev::check_for_immutable(const char* vol_name)
+{
+   return check_for_attr(vol_name, FS_IMMUTABLE_FL);
+}
+#else
+bool file_dev::set_immutable(const char *vol_name)
+{
+   return true;
+}
+
+bool file_dev::clear_immutable(const char *vol_name)
+{
+   return true;
+}
+
+bool file_dev::check_for_immutable(const char* vol_name)
+{
+   return true;
+}
+#endif // HAVE_IMMUTABLE_FL
 
 /*
  * Position device to end of medium (end of data)
index e7c2c011d7bc41cfcec1564c51391e9271df2656..ec1a26b569e43a95252344fb1c2ae78bcc7e4bf3 100644 (file)
 #define __FILE_DEV_
 
 class file_dev : public DEVICE {
+private:
+   void get_volume_fpath(const char *vol_name, POOLMEM **buf);
+   bool modify_fattr(const char *vol_name, int attr, bool set);
+   bool check_for_attr(const char *vol_name, int attr);
+   bool set_fattr(const char *vol_name, int attr);
+   bool clear_fattr(const char *vol_name, int attr);
+   bool check_for_immutable(const char *vol_name);
+   bool append_open_needed(const char *vol_name);
+   bool is_attribute_supported(int attr);
+
 public:
 
    file_dev() { };
@@ -33,6 +43,11 @@ public:
    bool open_device(DCR *dcr, int omode);
    const char *print_type();
    virtual int device_specific_init(JCR *jcr, DEVRES *device);
+   bool set_append_only(const char *vol_name);
+   bool clear_append_only(const char *vol_name);
+   bool set_immutable(const char *vol_name);
+   bool clear_immutable(const char *vol_name);
+   bool check_volume_protection_time(const char *vol_name);
 };
 
 #endif /* __FILE_DEV_ */
index a01db1d3b9052ddc228c390dd388a0c568834455..479f1eff5d34e95988594c6e4dcc26f7408b7906 100644 (file)
@@ -294,6 +294,13 @@ read_volume:
          mark_volume_in_error();
          goto mount_next_vol;
       }
+
+      /* Set the append flag on the volume */
+      if (!dev->set_append_only(getVolCatName())) {
+         Jmsg(jcr, M_WARNING, 0, _("Unable to set the APPEND flag on the volume: %s, err: %s\n"),
+              getVolCatName(), dev->bstrerror());
+         goto mount_next_vol;
+      }
    } else {
       /*
        * OK, at this point, we have a valid Bacula label, but
index 2d8927cabc8e1e7bed931d0697182acf03b49970..6f74eca6b2141926dce095a0a4fa2393293f85c8 100644 (file)
@@ -159,6 +159,8 @@ static RES_ITEM dev_items[] = {
    {"Enabled",               store_bool, ITEM(res_dev.enabled), 0, ITEM_DEFAULT, 1},
    {"AutoSelect",            store_bool, ITEM(res_dev.autoselect), 0, ITEM_DEFAULT, 1},
    {"ReadOnly",              store_bool, ITEM(res_dev.read_only), 0, ITEM_DEFAULT, 0},
+   {"ProtectVolumes",        store_bool, ITEM(res_dev.protect_vols), 0, ITEM_DEFAULT, 0},
+   {"MinimumVolumeProtectionTime",    store_time,   ITEM(res_dev.min_volume_protection_time), 0, ITEM_DEFAULT, 30*24*60*60},
    {"ChangerDevice",         store_strname,ITEM(res_dev.changer_name), 0, 0, 0},
    {"ControlDevice",         store_strname,ITEM(res_dev.control_name), 0, 0, 0},
    {"ChangerCommand",        store_strname,ITEM(res_dev.changer_command), 0, 0, 0},
index a9ec41c50650c578a7b651b44f4417df0b54716f..31ad57de0efe99750a629ea9fa85ae2491143a02 100644 (file)
@@ -223,6 +223,8 @@ public:
    bool enabled;                      /* Set when enabled (default) */
    bool autoselect;                   /* Automatically select from AutoChanger */
    bool read_only;                    /* Drive is read only */
+   bool protect_vols;                 /* Protect Volumes */
+   utime_t min_volume_protection_time;         /* Minimum Volume Protection Time */
    uint32_t drive_index;              /* Autochanger drive index */
    uint32_t cap_bits;                 /* Capabilities of this device */
    utime_t max_changer_wait;          /* Changer timeout */