You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

153 lines
3.5 KiB

#include "fs_copyfile.hpp"
#include "fs_attr.hpp"
#include "fs_close.hpp"
#include "fs_copydata.hpp"
#include "fs_fchmod.hpp"
#include "fs_fchown.hpp"
#include "fs_fcntl.hpp"
#include "fs_file_unchanged.hpp"
#include "fs_fstat.hpp"
#include "fs_futimens.hpp"
#include "fs_mktemp.hpp"
#include "fs_open.hpp"
#include "fs_rename.hpp"
#include "fs_unlink.hpp"
#include "fs_xattr.hpp"
#include "scope_guard.hpp"
#include <fcntl.h>
#include <signal.h>
static
bool
_ignorable_error(const int err_)
{
switch(err_)
{
case ENOTTY:
case ENOTSUP:
#if ENOTSUP != EOPNOTSUPP
case EOPNOTSUPP:
#endif
return true;
}
return false;
}
int
fs::copyfile(const int src_fd_,
const struct stat &src_st_,
const int dst_fd_)
{
int rv;
rv = fs::copydata(src_fd_,dst_fd_,src_st_.st_size);
if(rv == -1)
return -1;
rv = fs::xattr::copy(src_fd_,dst_fd_);
if((rv == -1) && !::_ignorable_error(errno))
return -1;
rv = fs::attr::copy(src_fd_,dst_fd_,FS_ATTR_CLEAR_IMMUTABLE);
if((rv == -1) && !::_ignorable_error(errno))
return -1;
rv = fs::fchown_check_on_error(dst_fd_,src_st_);
if(rv == -1)
return -1;
rv = fs::fchmod_check_on_error(dst_fd_,src_st_);
if(rv == -1)
return -1;
rv = fs::futimens(dst_fd_,src_st_);
if(rv == -1)
return -1;
return 0;
}
// Limitations:
// * Doesn't handle immutable files well. Will ignore the flag on attr
// copy.
// * Does not handle non-regular files.
int
fs::copyfile(const std::filesystem::path &src_,
const std::filesystem::path &dst_,
const u32 flags_)
{
int rv;
int src_fd;
int dst_fd;
struct stat src_st = {0};
std::string dst_tmppath;
struct sigaction old_act;
struct sigaction new_act;
// Done in case fcntl(F_SETLEASE) works.
new_act.sa_handler = SIG_IGN;
new_act.sa_flags = 0;
sigemptyset(&new_act.sa_mask);
sigaction(SIGIO,&new_act,&old_act);
DEFER { sigaction(SIGIO,&old_act,NULL); };
src_fd = fs::open(src_,O_RDONLY|O_NOFOLLOW);
if(src_fd < 0)
return src_fd;
DEFER { fs::close(src_fd); };
while(true)
{
std::tie(dst_fd,dst_tmppath) = fs::mktemp(dst_,O_RDWR);
if(dst_fd < 0)
return dst_fd;
DEFER { fs::close(dst_fd); };
// If it fails or is unsupported... so be it. This will help
// limit the possibility of the file being modified while the
// copy happens. Opening read-only is fine but open for write or
// truncate will block for others till this finishes or the
// kernel wide timeout (/proc/sys/fs/lease-break-time).
fs::fcntl_setlease_rdlck(src_fd);
DEFER { fs::fcntl_setlease_unlck(src_fd); };
// For comparison after the copy to see if the file was
// modified. This could be made more thorough by adding some
// hashing but probably overkill. Also used to provide size and
// other details for data copying.
rv = fs::fstat(src_fd,&src_st);
if(rv < 0)
return rv;
rv = fs::copyfile(src_fd,src_st,dst_fd);
if(rv < 0)
{
if(flags_ & FS_COPYFILE_CLEANUP_FAILURE)
fs::unlink(dst_tmppath);
return rv;
}
rv = fs::file_changed(src_fd,src_st);
if(rv == FS_FILE_CHANGED)
{
fs::unlink(dst_tmppath);
continue;
}
rv = fs::rename(dst_tmppath,dst_);
if((rv < 0) && (flags_ & FS_COPYFILE_CLEANUP_FAILURE))
fs::unlink(dst_tmppath);
break;
}
return rv;
}