}
}
-
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");
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"),
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());
#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;
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;
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;
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
/* 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);
/* 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());
}
}
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();};
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.
}
}
+ 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"),
/* 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,
*/
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;
}
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;
}
/* 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;
}
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;
}
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;
}
#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
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);
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_ */
{"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},
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 */
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();
}