]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add Storage Daemon SetReadOnly directive
authorEric Bollengier <eric@baculasystems.com>
Mon, 10 Oct 2022 10:13:57 +0000 (12:13 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 14 Sep 2023 11:56:59 +0000 (13:56 +0200)
bacula/src/findlib/attribs.c
bacula/src/lib/bsys.c
bacula/src/lib/protos.h
bacula/src/stored/block_util.c
bacula/src/stored/dev.h
bacula/src/stored/file_dev.c
bacula/src/stored/file_dev.h
bacula/src/stored/stored_conf.c
bacula/src/stored/stored_conf.h
bacula/src/stored/vol_mgr.c

index 96552f4d081036ffe7cc24481b66466ca3cb0673..64232c511b23a7a4d142bda693026e937df35956 100644 (file)
@@ -98,11 +98,9 @@ void set_own_mod(ATTR *attr, char *path, uid_t owner, gid_t group, mode_t mode)
    }
 }
 
-
 bool set_mod_own_time(JCR *jcr, BFILE *ofd, ATTR *attr)
 {
    bool ok = true;
-   struct utimbuf ut;
 
    ASSERTD(attr->type != FT_LNK, "function set_mod_own_time() not designed to handle SYMLINK");
 
@@ -134,25 +132,13 @@ bool set_mod_own_time(JCR *jcr, BFILE *ofd, ATTR *attr)
          ok = false;
       }
 
-#ifdef HAVE_FUTIMES
-      struct timeval times[2];
-      times[0].tv_sec = attr->statp.st_atime;
-      times[0].tv_usec = 0;
-      times[1].tv_sec = attr->statp.st_mtime;
-      times[1].tv_usec = 0;
-      if (futimes(ofd->fid, times) < 0 && print_error(jcr)) {
-#else
-      ut.actime = attr->statp.st_atime;
-      ut.modtime = attr->statp.st_mtime;
-      //bclose(ofd);
-      if (utime(attr->ofname, &ut) < 0 && print_error(jcr)) {
-#endif
+      if (set_own_time(ofd->fid, attr->ofname,  attr->statp.st_atime, attr->statp.st_mtime) < 0) {
          berrno be;
          Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
             attr->ofname, be.bstrerror());
          ok = false;
       }
-   } else {
+   } else {                     // Not open
       if (lchown(attr->ofname, attr->statp.st_uid, attr->statp.st_gid) < 0 && print_error(jcr)) {
          berrno be;
          Jmsg2(jcr, M_ERROR, 0, _("Unable to set file owner %s: ERR=%s\n"),
@@ -165,13 +151,11 @@ bool set_mod_own_time(JCR *jcr, BFILE *ofd, ATTR *attr)
             attr->ofname, be.bstrerror());
          ok = false;
       }
+
       /*
        * Reset file times.
        */
-      ut.actime = attr->statp.st_atime;
-      ut.modtime = attr->statp.st_mtime;
-
-      if (utime(attr->ofname, &ut) < 0 && print_error(jcr)) {
+      if (set_own_time(-1, attr->ofname,  attr->statp.st_atime, attr->statp.st_mtime) < 0 && print_error(jcr)) {
          berrno be;
          Jmsg2(jcr, M_ERROR, 0, _("Unable to set file times %s: ERR=%s\n"),
             attr->ofname, be.bstrerror());
index 19f7a9d70e9591a68b1707d646776da590b770fe..829fc48a6901e1aad66188563992d1e107c2127d 100644 (file)
@@ -37,6 +37,9 @@
 #else
 #include <sys/utsname.h>
 #endif
+#ifdef HAVE_UTIME_H
+#include <utime.h>
+#endif
 
 static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t timer = PTHREAD_COND_INITIALIZER;
@@ -1673,16 +1676,37 @@ FILE *bfopen(const char *path, const char *mode)
    return fp;
 }
 
+/* Helper method to use fchown() whenever possible, chown() otherwise */
+int bstat(int fd, const char *path, struct stat *sp)
+{
+#ifdef HAVE_FSTAT
+   if (fd >= 0) {                // fd can be 0 as well
+      return fstat(fd, sp);
+
+   } else if (path) {
+      return stat(path, sp);
+
+   } else {
+      Dmsg0(100, "bchown failed, neither the fd nor path was specified\n");
+      return -1;
+   }
+#else
+   return stat(path, sp);
+#endif
+}
+
 /* Helper method to use fchown() whenever possible, chown() otherwise */
 int bchown(int fd, const char *path, uid_t uid, gid_t gid)
 {
 #ifdef HAVE_FCHOWN
-   if (fd) {
-      return fchown(fd, uid, gid);
+   if (fd >= 0) {
       Dmsg3(100, "Calling fchown for file descriptor %d uid: %ld gid: %ld\n", fd, uid, gid);
+      return fchown(fd, uid, gid);
+
    } else if (path) {
       Dmsg3(100, "Calling chown for file %s uid: %ld gid: %ld\n", path, uid, gid);
       return chown(path, uid, gid);
+
    } else {
       Dmsg0(100, "bchown failed, neither the fd nor path was specified\n");
       return -1;
@@ -1697,12 +1721,14 @@ int bchown(int fd, const char *path, uid_t uid, gid_t gid)
 int bchmod(int fd, const char *path, mode_t mode)
 {
 #ifdef HAVE_FCHOWN
-   if (fd) {
+   if (fd >= 0) {                // TODO: fd can be 0
       Dmsg2(100, "Calling chmod for file descriptor %d mode: %d\n", fd, mode);
       return fchmod(fd, mode);
+
    } else if (path) {
       Dmsg2(100, "Calling chmod for file: %s mode: %d\n", path, mode);
       return chmod(path, mode);
+
    } else {
       Dmsg0(100, "bchmod failed, neither the fd nor path was specified\n");
       return -1;
@@ -2002,6 +2028,33 @@ void get_path_and_fname(const char *name, char **path, char **fname)
       free(cpath);
       free(cargv0);
    }
+
+/* Set atime/mtime for a given file
+ * Must be called on open file
+ */
+int set_own_time(int fd, const char *path, btime_t atime, btime_t mtime)
+{
+#ifdef HAVE_FUTIMES
+   struct timeval times[2];
+   times[0].tv_sec = atime;
+   times[0].tv_usec = 0;
+   times[1].tv_sec = mtime;
+   times[1].tv_usec = 0;
+
+   if (fd > 0) {
+      if (futimes(fd, times) == 0) {
+         return 0;
+      }
+   }
+#endif
+   struct utimbuf ut;
+   ut.actime = atime;
+   ut.modtime = mtime;
+
+   if (utime(path, &ut) == 0) {
+      return 0;
+   }
+   return -1;
 }
 
 #ifdef TEST_PROGRAM
index 86fa85e23d70e49e0b4b573570fbfc291a62bd99..dbdd2d87c9cd167ec35f68ed5b2a41406651d0c9 100644 (file)
@@ -72,6 +72,8 @@ void display_collector_types(HPKT &hpkt);
 /* bsys.c */
 
 void get_path_and_fname(const char *file, char **path, char **fname);
+int set_own_time(int fd, const char *path, btime_t atime, btime_t mtime);
+int bstat(int fd, const char *path, struct stat *sp);
 const char *get_timezone();
 int get_user_home_directory(const char *user, POOLMEM *&home);
 int get_home_directories(const char *grpname, alist *dirs);
index c669c7459f53cb393e7d2003d260861d0ca73d9b..3d05e92869da4d1be1ca085dacee021144b2e75d 100644 (file)
@@ -626,6 +626,25 @@ bool is_user_volume_size_reached(DCR *dcr, bool quiet)
             /* 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);
+         } else if (!quiet) {
+            Jmsg(dcr->jcr, M_INFO, 0, _("Marking Volume \"%s\" as immutable\n"),
+                 dev->getVolCatName());
+         }
+      }
+
+      if (dev->device->set_vol_read_only) {
+         /* Set volume as immutable/read only */
+         if (dev->set_atime(time(NULL) + dev->device->min_volume_protection_time) < 0) {
+            Jmsg(dcr->jcr, M_WARNING, 0, _("Failed to set the volume %s on device %s in atime retention, ERR=%s.\n"),
+                 dev->getVolCatName(), dev->print_name(), dev->errmsg);
+         }
+         if (dev->set_readonly() < 0) {
+            /* We may proceed with that but warn the user */
+            Jmsg(dcr->jcr, M_WARNING, 0, _("Failed to set the volume %s on device %s in read-only, ERR=%s.\n"),
+                 dev->getVolCatName(), dev->print_name(), dev->errmsg);
+         } else if (!quiet) {
+            Jmsg(dcr->jcr, M_INFO, 0, _("Marking Volume \"%s\" as read-only\n"),
+                 dev->getVolCatName());
          }
       }
 
index 54eaf9f1998d3aee878990dcfd3e9ef655dad9c1..83b85bac512d5ff83d4793b419db5d078761f2f1 100644 (file)
@@ -608,6 +608,10 @@ public:
    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 false; };
+   virtual bool check_for_read_only(const char *vol_name) { return false; };
+   virtual int set_writable() { pm_strcpy(errmsg, _("Not implemented")); return -1;};
+   virtual int set_readonly() { pm_strcpy(errmsg, _("Not implemented")); return -1;};
+   virtual int set_atime(btime_t val) { pm_strcpy(errmsg, _("Not implemented")); return -1;};
    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 aa878cfe01d77147a83e72bf7737ed5fb4ecd3b2..bf207c1c5ec09683a31fa573dc7a18546949fc57 100644 (file)
@@ -248,6 +248,49 @@ bool file_dev::open_device(DCR *dcr, int omode)
    return m_fd >= 0;
 }
 
+int file_dev::set_writable()
+{
+   POOL_MEM fname;
+   get_volume_fpath(getVolCatName(), fname.handle());
+   int ret = bchmod(m_fd, fname.c_str(), 0600);
+   if (ret < 0) {
+      berrno be;
+      Mmsg(errmsg, _("Unable to change permission to 0600. ERR=%s\n"), be.bstrerror());
+   }
+   return ret;
+}
+
+int file_dev::set_readonly()
+{
+   POOL_MEM fname;
+   get_volume_fpath(getVolCatName(), fname.handle());
+   int ret = bchmod(m_fd, fname.c_str(), 0400);
+   if (ret < 0) {
+      berrno be;
+      Mmsg(errmsg, _("Unable to change permission to 0400. ERR=%s\n"), be.bstrerror());
+   }
+   return ret;
+}
+
+int file_dev::set_atime(btime_t val)
+{
+   struct stat sp;
+   int ret;
+   POOL_MEM fname;
+   get_volume_fpath(getVolCatName(), fname.handle());
+   if (bstat(m_fd, fname.c_str(), &sp) < 0) {
+      berrno be;
+      Mmsg(errmsg, _("Unable to stat %s. ERR=%s\n"), fname.c_str(), be.bstrerror());
+      return -1;
+   }
+   ret = set_own_time(m_fd, fname.c_str(), val, sp.st_mtime);
+   if (ret < 0) {
+      berrno be;
+      Mmsg(errmsg, _("Unable to set atime/mtime to %s. ERR=%s\n"), fname.c_str(), be.bstrerror());
+   }
+   return ret;
+}
+
 /*
  * Truncate a volume.  If this is aligned disk, we
  *    truncate both volumes.
@@ -279,6 +322,14 @@ bool DEVICE::truncate(DCR *dcr)
       }
    }
 
+   if (dev->device->set_vol_read_only) {
+      if (set_writable() < 0) {
+         Mmsg3(errmsg, _("Unable to set write permission for volume %s on device %s. %s\n"),
+               dcr->VolumeName, print_name(), dev->errmsg);
+         return false;
+      }
+   }
+
    if (ftruncate(dev->m_fd, 0) != 0) {
       berrno be;
       Mmsg2(errmsg, _("Unable to truncate device %s. ERR=%s\n"),
@@ -574,7 +625,9 @@ void file_dev::get_volume_fpath(const char *vol_name, POOLMEM **fname)
 /* 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.
+ * current system time is compared against m_time of volume file for immutable flag
+ *
+ * For the read-only (on NetApp Snaplock for example), we check a_time
  *
  * @return true  if volume can be reused
  * @return false if volume's protection time hasn't expired yet,
@@ -582,8 +635,8 @@ void file_dev::get_volume_fpath(const char *vol_name, POOLMEM **fname)
  */
 bool file_dev::check_volume_protection_time(const char *vol_name)
 {
-   if (!device->set_vol_immutable) {
-      Dmsg1(DT_VOLUME|50, "SetVolumeImmutable turned off for volume: %s\n", vol_name);
+   if (!device->set_vol_immutable && !device->set_vol_read_only) {
+      Dmsg1(DT_VOLUME|50, "SetVolumeImmutable/SetVolumeReadOnly turned off for volume: %s\n", vol_name);
       return true;
    }
 
@@ -594,7 +647,7 @@ bool file_dev::check_volume_protection_time(const char *vol_name)
       Dmsg1(DT_VOLUME|50, _("Immutable flag cannot be cleared for volume: %s, "
                     "because Minimum Volume Protection Time is set to 0\n"),
                     vol_name);
-      Mmsg(errmsg, _("Immutable flag cannot be cleared for volume: %s, "
+      Mmsg(errmsg, _("Immutable/ReadOnly flag cannot be cleared for volume: %s, "
                     "because Minimum Volume Protection Time is set to 0\n"),
                     vol_name);
       return false;
@@ -618,22 +671,27 @@ bool file_dev::check_volume_protection_time(const char *vol_name)
    }
 
    /* 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 expiration_time;
    time_t now = time(NULL);
+   if (device->set_vol_immutable) {
+      expiration_time = sp.st_mtime + device->min_volume_protection_time;
+   } else {                     // ReadOnly, we check both and we take the biggest one
+      expiration_time = MAX(sp.st_atime, sp.st_mtime + device->min_volume_protection_time);
+   }
    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, "
+      Mmsg1(errmsg, _("Immutable/ReadOnly flag cannot be cleared for volume: %s, "
                       "because Minimum Volume Protection Time hasn't expired yet.\n"),
             vol_name);
-      Dmsg3(DT_VOLUME|50, "Immutable flag cannot be cleared for volume: %s, "
+      Dmsg3(DT_VOLUME|50, "Immutable/ReadOnly flag cannot be cleared for volume: %s, "
                     "because:\nexpiration time: %s\nnow: %s\n",
                     vol_name, dt, dt2);
       return false;
    }
 
-   Dmsg1(DT_VOLUME|50, "Immutable flag can be cleared for volume: %s\n", vol_name);
+   Dmsg1(DT_VOLUME|50, "Immutable/ReadOnly flag can be cleared for volume: %s\n", vol_name);
    return true;
 }
 
@@ -657,7 +715,7 @@ bool file_dev::check_for_attr(const char *vol_name, int attr)
 
    if ((tmp_fd = d_open(fname.c_str(), O_RDONLY|O_CLOEXEC)) < 0) {
       berrno be;
-      Dmsg2(DT_VOLUME|50, "Failed to open %s, ERR=%s", fname.c_str(), be.bstrerror());
+      Dmsg2(DT_VOLUME|50, "Failed to open %s, ERR=%s\n", fname.c_str(), be.bstrerror());
       Mmsg2(errmsg, "Failed to open %s, ERR=%s", fname.c_str(), be.bstrerror());
       return ret;
    }
@@ -665,7 +723,7 @@ bool file_dev::check_for_attr(const char *vol_name, int attr)
    ioctl_ret = d_ioctl(tmp_fd, FS_IOC_GETFLAGS, (char *)&get_attr);
    if (ioctl_ret < 0) {
       berrno be;
-      Dmsg2(DT_VOLUME|50, "Failed to get attributes for %s, ERR=%s", fname.c_str(), be.bstrerror());
+      Dmsg2(DT_VOLUME|50, "Failed to get attributes for %s, ERR=%s\n", fname.c_str(), be.bstrerror());
       Mmsg2(errmsg, "Failed to get attributes for %s, ERR=%s", fname.c_str(), be.bstrerror());
    } else {
       ret = get_attr & attr;
@@ -854,6 +912,29 @@ bool file_dev::check_for_immutable(const char* vol_name)
 }
 #endif // HAVE_IMMUTABLE_FL
 
+bool file_dev::check_for_read_only(const char *vol)
+{   
+   if (!device->set_vol_read_only) {
+      return false;              // Feature not used
+   }
+
+   struct stat sp;
+   POOL_MEM fname;
+   get_volume_fpath(vol, fname.handle());
+
+   if (stat(fname.c_str(), &sp) < 0) {
+      return false;              // Not found, no problem?
+   }
+
+   if ((sp.st_mode & 07777) == S_IRUSR) {
+      return true;
+   }
+
+   return false;
+}
+
+bool check_for_immutable(const char *vol_name);
+
 /*
  * Position device to end of medium (end of data)
  *  Returns: true  on succes
index 83baf8a0f6e4c3dc8fa9789bd0a3bff133b92fd2..c029757298656c2d20e17b02eac5f713b4dfdf00 100644 (file)
@@ -31,6 +31,7 @@ private:
    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 check_for_read_only(const char *vol_name);
    bool append_open_needed(const char *vol_name);
    bool is_attribute_supported(int attr);
 
@@ -50,6 +51,9 @@ public:
    bool check_volume_protection_time(const char *vol_name);
    bool get_os_device_freespace();
    bool is_fs_nearly_full(uint64_t threshold);
+   int set_writable();
+   int set_readonly();
+   int set_atime(btime_t val);
 };
 
 #endif /* __FILE_DEV_ */
index 47b25f74d09b951f36e473c02a59957055bb74c9..3dabaff734fdd97536a5d9d64fa2032411029234 100644 (file)
@@ -161,6 +161,7 @@ static RES_ITEM dev_items[] = {
    {"ReadOnly",              store_bool, ITEM(res_dev.read_only), 0, ITEM_DEFAULT, 0},
    {"SetVolumeAppendOnly",   store_bool, ITEM(res_dev.set_vol_append_only), 0, ITEM_DEFAULT, 0},
    {"SetVolumeImmutable",    store_bool, ITEM(res_dev.set_vol_immutable), 0, ITEM_DEFAULT, 0},
+   {"SetVolumeReadOnly",     store_bool, ITEM(res_dev.set_vol_read_only), 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},
index 3c3221a61e62cff79e149b5bd60f2733c639185d..b6190a2029cdc687ccb753f2fa9ffa42e58d92ed 100644 (file)
@@ -237,6 +237,7 @@ public:
    bool read_only;                    /* Drive is read only */
    bool set_vol_append_only;          /* Set 'Append Only' filesystem flag for volumes */
    bool set_vol_immutable;            /* Set 'Immutable' filesystem flag for volumes */
+   bool set_vol_read_only;            /* Set permission of volumes when marking them as Full/Used */
    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 */
index 089bd9b83af59dfc3bbf66c3c878b31cd22ad1dd..ef0576470df4607930f881ee3e022f97db499ebb 100644 (file)
@@ -844,6 +844,15 @@ bool DCR::can_i_write_volume()
       return false;
    }
 
+   if (dev->device->set_vol_read_only       &&
+       dev->check_for_read_only(VolumeName) &&
+       dev->check_volume_protection_time(VolumeName) == false) {
+      MmsgD1(dbglvl, jcr->errmsg, _("Skipping Volume %s, "
+                                    "because Volume's Protection Period has not expired yet\n"),
+             VolumeName);
+      return false;
+   }
+
    return can_i_use_volume();
 }