From: Michal Rakowski Date: Thu, 2 Sep 2021 14:32:52 +0000 (+0200) Subject: Add support for the APPEND and IMMUTABLE flags for File Volumes X-Git-Tag: Beta-15.0.0~837 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c91d9b33b8271c90d827c2eb43b0e79e9fd460c5;p=thirdparty%2Fbacula.git Add support for the APPEND and IMMUTABLE flags for File Volumes --- diff --git a/bacula/platforms/systemd/bacula-sd.service.in b/bacula/platforms/systemd/bacula-sd.service.in index 0e7b50eb9..c3637a22f 100644 --- a/bacula/platforms/systemd/bacula-sd.service.in +++ b/bacula/platforms/systemd/bacula-sd.service.in @@ -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 diff --git a/bacula/src/stored/block_util.c b/bacula/src/stored/block_util.c index 1fde128cd..b770b193d 100644 --- a/bacula/src/stored/block_util.c +++ b/bacula/src/stored/block_util.c @@ -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(), diff --git a/bacula/src/stored/dev.h b/bacula/src/stored/dev.h index 06ca67e38..95017fabb 100644 --- a/bacula/src/stored/dev.h +++ b/bacula/src/stored/dev.h @@ -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();}; diff --git a/bacula/src/stored/dircmd.c b/bacula/src/stored/dircmd.c index 6bb2a89d2..3838cc65d 100644 --- a/bacula/src/stored/dircmd.c +++ b/bacula/src/stored/dircmd.c @@ -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); diff --git a/bacula/src/stored/file_dev.c b/bacula/src/stored/file_dev.c index 2d8dce27e..d547bbdcf 100644 --- a/bacula/src/stored/file_dev.c +++ b/bacula/src/stored/file_dev.c @@ -28,6 +28,10 @@ #include "bacula.h" #include "stored.h" +#ifdef HAVE_LINUX_OS +#include +#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) diff --git a/bacula/src/stored/file_dev.h b/bacula/src/stored/file_dev.h index e7c2c011d..ec1a26b56 100644 --- a/bacula/src/stored/file_dev.h +++ b/bacula/src/stored/file_dev.h @@ -24,6 +24,16 @@ #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_ */ diff --git a/bacula/src/stored/mount.c b/bacula/src/stored/mount.c index a01db1d3b..479f1eff5 100644 --- a/bacula/src/stored/mount.c +++ b/bacula/src/stored/mount.c @@ -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 diff --git a/bacula/src/stored/stored_conf.c b/bacula/src/stored/stored_conf.c index 2d8927cab..6f74eca6b 100644 --- a/bacula/src/stored/stored_conf.c +++ b/bacula/src/stored/stored_conf.c @@ -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}, diff --git a/bacula/src/stored/stored_conf.h b/bacula/src/stored/stored_conf.h index a9ec41c50..31ad57de0 100644 --- a/bacula/src/stored/stored_conf.h +++ b/bacula/src/stored/stored_conf.h @@ -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 */