From: Eric Bollengier Date: Mon, 10 Oct 2022 10:13:57 +0000 (+0200) Subject: Add Storage Daemon SetReadOnly directive X-Git-Tag: Beta-15.0.0~414 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a15dd6fe03ab584d14ab2af89be11b9bc9ccae65;p=thirdparty%2Fbacula.git Add Storage Daemon SetReadOnly directive --- diff --git a/bacula/src/findlib/attribs.c b/bacula/src/findlib/attribs.c index 96552f4d0..64232c511 100644 --- a/bacula/src/findlib/attribs.c +++ b/bacula/src/findlib/attribs.c @@ -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()); diff --git a/bacula/src/lib/bsys.c b/bacula/src/lib/bsys.c index 19f7a9d70..829fc48a6 100644 --- a/bacula/src/lib/bsys.c +++ b/bacula/src/lib/bsys.c @@ -37,6 +37,9 @@ #else #include #endif +#ifdef HAVE_UTIME_H +#include +#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 diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index 86fa85e23..dbdd2d87c 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -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); diff --git a/bacula/src/stored/block_util.c b/bacula/src/stored/block_util.c index c669c7459..3d05e9286 100644 --- a/bacula/src/stored/block_util.c +++ b/bacula/src/stored/block_util.c @@ -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()); } } diff --git a/bacula/src/stored/dev.h b/bacula/src/stored/dev.h index 54eaf9f19..83b85bac5 100644 --- a/bacula/src/stored/dev.h +++ b/bacula/src/stored/dev.h @@ -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();}; diff --git a/bacula/src/stored/file_dev.c b/bacula/src/stored/file_dev.c index aa878cfe0..bf207c1c5 100644 --- a/bacula/src/stored/file_dev.c +++ b/bacula/src/stored/file_dev.c @@ -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 diff --git a/bacula/src/stored/file_dev.h b/bacula/src/stored/file_dev.h index 83baf8a0f..c02975729 100644 --- a/bacula/src/stored/file_dev.h +++ b/bacula/src/stored/file_dev.h @@ -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_ */ diff --git a/bacula/src/stored/stored_conf.c b/bacula/src/stored/stored_conf.c index 47b25f74d..3dabaff73 100644 --- a/bacula/src/stored/stored_conf.c +++ b/bacula/src/stored/stored_conf.c @@ -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}, diff --git a/bacula/src/stored/stored_conf.h b/bacula/src/stored/stored_conf.h index 3c3221a61..b6190a202 100644 --- a/bacula/src/stored/stored_conf.h +++ b/bacula/src/stored/stored_conf.h @@ -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 */ diff --git a/bacula/src/stored/vol_mgr.c b/bacula/src/stored/vol_mgr.c index 089bd9b83..ef0576470 100644 --- a/bacula/src/stored/vol_mgr.c +++ b/bacula/src/stored/vol_mgr.c @@ -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(); }