io_posix.cc (fstransform-0.9.3-src) | : | io_posix.cc (fstransform-0.9.4) | ||
---|---|---|---|---|
skipping to change at line 80 | skipping to change at line 80 | |||
#ifdef FT_HAVE_UTIME_H | #ifdef FT_HAVE_UTIME_H | |||
# include <utime.h> // " " " | # include <utime.h> // " " " | |||
#endif | #endif | |||
#include "../assert.hh" // for ff_assert() | #include "../assert.hh" // for ff_assert() | |||
#include "../log.hh" // for ff_log() | #include "../log.hh" // for ff_log() | |||
#include "../misc.hh" // for ff_min2() | #include "../misc.hh" // for ff_min2() | |||
#include "disk_stat.hh" // for fm_disk_stat::THRESHOLD_MIN | #include "disk_stat.hh" // for fm_disk_stat::THRESHOLD_MIN | |||
#include "io_posix.hh" // for fm_io_posix | #include "io_posix.hh" // for fm_io_posix | |||
#include "io_posix_dir.hh" // for fm_io_posix_dir | #include "io_posix_dir.hh" // for ft_io_posix_dir | |||
#include "util_posix.hh" // for ff_posix_exec_silent() | #include "util_posix.hh" // for ff_posix_exec_silent() | |||
#ifndef PATH_MAX | #ifndef PATH_MAX | |||
# define PATH_MAX 4096 | # define PATH_MAX 4096 | |||
#endif /* PATH_MAX */ | #endif /* PATH_MAX */ | |||
FT_IO_NAMESPACE_BEGIN | FT_IO_NAMESPACE_BEGIN | |||
/** default constructor */ | /** default constructor */ | |||
fm_io_posix::fm_io_posix() | fm_io_posix::fm_io_posix() | |||
skipping to change at line 146 | skipping to change at line 146 | |||
return half_free_space > bytes_to_write | return half_free_space > bytes_to_write | |||
&& half_free_space - bytes_to_write > bytes_copied_since_last_check; | && half_free_space - bytes_to_write > bytes_copied_since_last_check; | |||
} | } | |||
/** | /** | |||
* add bytes_just_written to bytes_copied_since_last_check. | * add bytes_just_written to bytes_copied_since_last_check. | |||
* | * | |||
* if bytes_copied_since_last_check >= PERIODIC_CHECK_FREE_SPACE or >= 50% of fr ee space, | * if bytes_copied_since_last_check >= PERIODIC_CHECK_FREE_SPACE or >= 50% of fr ee space, | |||
* reset bytes_copied_since_last_check to zero and call check_free_space() | * reset bytes_copied_since_last_check to zero and call check_free_space() | |||
*/ | */ | |||
int fm_io_posix::periodic_check_free_space(ft_size bytes_just_written, ft_uoff b ytes_to_write) | int fm_io_posix::periodic_check_free_space(ft_uoff bytes_just_written, ft_uoff b ytes_to_write) | |||
{ | { | |||
add_work_done(bytes_just_written); | add_work_done(bytes_just_written); | |||
bytes_copied_since_last_check += bytes_just_written; | bytes_copied_since_last_check += bytes_just_written; | |||
int err = 0; | int err = 0; | |||
if (!enough_free_space(bytes_to_write)) { | if (!enough_free_space(bytes_to_write)) { | |||
bytes_copied_since_last_check = 0; | bytes_copied_since_last_check = 0; | |||
err = check_free_space(); | err = check_free_space(); | |||
} | } | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* call 'disk_stat' twice: one time on source_root() and another on target_root( ). | * sync(), then call disk_stat() twice: one time on source_root() and another on target_root(). | |||
* return error if statvfs() fails or if free disk space becomes critically low | * return error if statvfs() fails or if free disk space becomes critically low | |||
*/ | */ | |||
int fm_io_posix::check_free_space() | int fm_io_posix::check_free_space() | |||
{ | { | |||
::sync(); // slow, but needed to get accurate disk stats when loop devices a re involved | sync(); // slow, but needed to get accurate disk stats when loop devices are involved | |||
int err = disk_stat(source_root().c_str(), source_stat()); | int err = disk_stat(source_root().c_str(), source_stat()); | |||
if (err == 0) | if (err == 0) | |||
err = disk_stat(target_root().c_str(), target_stat()); | err = disk_stat(target_root().c_str(), target_stat()); | |||
return err; | return err; | |||
} | } | |||
/** call ::sync(). slow, but needed to get accurate disk stats when loop devices | ||||
are involved */ | ||||
void fm_io_posix::sync() | ||||
{ | ||||
::sync(); | ||||
} | ||||
/** | /** | |||
* fill 'disk_stat' with information about the file-system containing 'path'. | * fill 'disk_stat' with information about the file-system containing 'path'. | |||
* return error if statvfs() fails or if free disk space becomes critically low | * return error if statvfs() fails or if free disk space becomes critically low | |||
*/ | */ | |||
int fm_io_posix::disk_stat(const char * path, fm_disk_stat & disk_stat) | int fm_io_posix::disk_stat(const char * path, fm_disk_stat & disk_stat) | |||
{ | { | |||
struct statvfs buf; | struct statvfs buf; | |||
int err = 0; | int err = 0; | |||
for (int i = 0; i < 2; i++) { | for (int i = 0; i < 2; i++) { | |||
skipping to change at line 204 | skipping to change at line 210 | |||
} | } | |||
err = disk_stat.set_free(disk_free); | err = disk_stat.set_free(disk_free); | |||
break; | break; | |||
} | } | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* use some file-system specific trickery and try to free some space. | * use some file-system specific trickery and try to free some space. | |||
*/ | */ | |||
void fm_io_posix::try_to_make_free_space(const char * path) | void fm_io_posix::try_to_make_free_space(const char * FT_ARG_UNUSED(path)) | |||
{ | { | |||
#if 0 | #if 0 | |||
/* | /* | |||
* we COULD run 'xfs_fsr <path>' and try to free some space on 'xfs' file-sy stems, | * we COULD run 'xfs_fsr <path>' and try to free some space on 'xfs' file-sy stems, | |||
* but at least on linux with an almost-full source device | * but at least on linux with an almost-full source device | |||
* xfs_fsr can WORSEN the problem by triggering 'loop write error' kernel er rors, | * xfs_fsr can WORSEN the problem by triggering 'loop write error' kernel er rors, | |||
* which mean the source device has not enough space to accommodate the loop file contents. | * which mean the source device has not enough space to accommodate the loop file contents. | |||
* typically this CORRUPTS the file system inside target (loop) device! | * this typically CORRUPTS the file system inside target (loop) device! | |||
*/ | */ | |||
const char * cmd = "xfs_fsr"; | const char * cmd = "xfs_fsr"; | |||
const char * const args[] = { cmd, path, NULL }; | const char * const args[] = { cmd, path, NULL }; | |||
if (ff_posix_exec_silent(cmd, args) == 0) | if (ff_posix_exec_silent(cmd, args) == 0) | |||
ff_log(FC_INFO, 0, "successfully executed '%s %s' to free some disk spac e", args[0], args[1]); | ff_log(FC_INFO, 0, "successfully executed '%s %s' to free some disk spac e", args[0], args[1]); | |||
#endif | #endif | |||
} | } | |||
/** | /** | |||
skipping to change at line 272 | skipping to change at line 278 | |||
/** | /** | |||
* move a single file/socket/special-device or a whole directory tree | * move a single file/socket/special-device or a whole directory tree | |||
*/ | */ | |||
int fm_io_posix::move(const ft_string & source_path, const ft_string & target_pa th) | int fm_io_posix::move(const ft_string & source_path, const ft_string & target_pa th) | |||
{ | { | |||
ft_stat stat; | ft_stat stat; | |||
const std::set<ft_string> & exclude_set = this->exclude_set(); | const std::set<ft_string> & exclude_set = this->exclude_set(); | |||
int err = 0; | int err = 0; | |||
ff_log(FC_DEBUG, 0, "move() `%s'\t-> `%s'", source_path.c_str(), tar get_path.c_str()); | ff_log(FC_DEBUG, 0, "`%s'\t-> `%s'", source_path.c_str(), target_path.c_str( )); | |||
do { | do { | |||
if (exclude_set.count(source_path) != 0) { | if (exclude_set.count(source_path) != 0) { | |||
ff_log(FC_INFO, 0, "move() skipped `%s', matches exclude list", sour ce_path.c_str()); | ff_log(FC_INFO, 0, "skipped `%s', matches exclude list", source_path .c_str()); | |||
break; | break; | |||
} | } | |||
if ((err = this->stat(source_path, stat)) != 0) | if ((err = this->stat(source_path, stat)) != 0) | |||
break; | break; | |||
if (fm_io_posix_is_file(stat)) { | if (fm_io_posix_is_file(stat)) { | |||
err = this->move_file(source_path, stat, target_path); | err = this->move_file(source_path, stat, target_path); | |||
break; | break; | |||
} else if (!fm_io_posix_is_dir(stat)) { | } else if (!fm_io_posix_is_dir(stat)) { | |||
err = this->move_special(source_path, stat, target_path); | err = this->move_special(source_path, stat, target_path); | |||
break; | break; | |||
} | } | |||
fm_io_posix_dir source_dir; | ft_io_posix_dir source_dir; | |||
if ((err = source_dir.open(source_path))) | if ((err = source_dir.open(source_path))) | |||
break; | break; | |||
/* | /* | |||
* we allow target_root() to exist already, but other target directories must NOT exist. | * we allow target_root() to exist already, but other target directories must NOT exist. | |||
* option '-f' drops this check, i.e. any target directory can exist alr eady | * option '-f' drops this check, i.e. any target directory can exist alr eady | |||
* | * | |||
* Exception: we allow a 'lost+found' directory to exist inside target_r oot() | * Exception: we allow a 'lost+found' directory to exist inside target_r oot() | |||
*/ | */ | |||
if ((err = this->create_dir(target_path)) != 0) | if ((err = this->create_dir(target_path)) != 0) | |||
break; | break; | |||
if ((err = this->periodic_check_free_space()) != 0) | if ((err = this->periodic_check_free_space()) != 0) | |||
break; | break; | |||
ft_string child_source = source_path, child_target = target_path; | ft_string child_source = source_path, child_target = target_path; | |||
child_source += '/'; | child_source += '/'; | |||
child_target += '/'; | child_target += '/'; | |||
fm_io_posix_dirent * dirent; | ft_io_posix_dirent * dirent; | |||
/* recurse on directory contents */ | /* recurse on directory contents */ | |||
while ((err = source_dir.next(dirent)) == 0 && dirent != NULL) { | while ((err = source_dir.next(dirent)) == 0 && dirent != NULL) { | |||
/* skip "." and ".." */ | /* skip "." and ".." */ | |||
if (!strcmp(".", dirent->d_name) || !strcmp("..", dirent->d_name)) | if (!strcmp(".", dirent->d_name) || !strcmp("..", dirent->d_name)) | |||
continue; | continue; | |||
child_source.resize(1 + source_path.size()); // faster than child_so urce = source_path + '/' | child_source.resize(1 + source_path.size()); // faster than child_so urce = source_path + '/' | |||
child_source += dirent->d_name; | child_source += dirent->d_name; | |||
skipping to change at line 376 | skipping to change at line 382 | |||
/* check inode_cache for hard links and recreate them */ | /* check inode_cache for hard links and recreate them */ | |||
err = this->hard_link(stat, target_path); | err = this->hard_link(stat, target_path); | |||
if (err == 0) { | if (err == 0) { | |||
/** hard link succeeded, no need to create the special-device */ | /** hard link succeeded, no need to create the special-device */ | |||
err = this->periodic_check_free_space(); | err = this->periodic_check_free_space(); | |||
break; | break; | |||
} else if (err == EAGAIN) { | } else if (err == EAGAIN) { | |||
/* no luck with inode_cache, proceed as usual */ | /* no luck with inode_cache, proceed as usual */ | |||
err = 0; | err = 0; | |||
} else { | } else { | |||
/** hard link failed */ | /** hard link() failed */ | |||
return err; | return err; | |||
} | } | |||
/* found a special device */ | /* found a special device */ | |||
if (S_ISCHR(stat.st_mode) || S_ISBLK(stat.st_mode) || S_ISSOCK(stat.st_m ode)) { | if (S_ISCHR(stat.st_mode) || S_ISBLK(stat.st_mode) || S_ISSOCK(stat.st_m ode)) { | |||
if (mknod(target, (stat.st_mode | 0600) & ~0077, stat.st_rdev) != 0) { | if (mknod(target, (stat.st_mode | 0600) & ~0077, stat.st_rdev) != 0) { | |||
if (!S_ISSOCK(stat.st_mode)) { | if (!S_ISSOCK(stat.st_mode)) { | |||
err = ff_log(FC_ERROR, errno, "failed to create target speci al device `%s'", target); | err = ff_log(FC_ERROR, errno, "failed to create target speci al device `%s'", target); | |||
break; | break; | |||
} | } | |||
skipping to change at line 408 | skipping to change at line 414 | |||
err = ff_log(FC_ERROR, errno, "failed to read source symbolic li nk `%s'", source); | err = ff_log(FC_ERROR, errno, "failed to read source symbolic li nk `%s'", source); | |||
break; | break; | |||
} | } | |||
link_to[link_len] = '\0'; | link_to[link_len] = '\0'; | |||
if (symlink(link_to, target) != 0) { | if (symlink(link_to, target) != 0) { | |||
err = ff_log(FC_ERROR, errno, "failed to create target symbolic link `%s'\t-> `%s'", target, link_to); | err = ff_log(FC_ERROR, errno, "failed to create target symbolic link `%s'\t-> `%s'", target, link_to); | |||
break; | break; | |||
} | } | |||
} else { | } else { | |||
ff_log(FC_ERROR, 0, "special device %s has unknown type 0%"FT_OLL", cannot create it", | ff_log(FC_ERROR, 0, "special device %s has unknown type 0%" FT_OLL " , cannot create it", | |||
source, (ft_ull)(stat.st_mode & ~07777)); | source, (ft_ull)(stat.st_mode & ~07777)); | |||
err = -EOPNOTSUPP; | err = -EOPNOTSUPP; | |||
break; | break; | |||
} | } | |||
if ((err = this->copy_stat(target, stat)) != 0) | if ((err = this->copy_stat(target, stat)) != 0) | |||
break; | break; | |||
if ((err = this->periodic_check_free_space()) != 0) | if ((err = this->periodic_check_free_space()) != 0) | |||
break; | break; | |||
} while (0); | } while (0); | |||
if (err == 0 && remove(source) != 0) | if (err == 0) | |||
err = ff_log(FC_ERROR, errno, "failed to remove source special device `% | err = remove_special(source); | |||
s'", source); | ||||
return err; | ||||
} | ||||
/** | ||||
* remove the special file 'source_path' | ||||
*/ | ||||
int fm_io_posix::remove_special(const char * source_path) | ||||
{ | ||||
int err = 0; | ||||
if (::remove(source_path) != 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to remove source special device `% | ||||
s'", source_path); | ||||
return err; | ||||
} | ||||
/** | ||||
* remove the regular file 'source_path' | ||||
*/ | ||||
int fm_io_posix::remove_file(const char * source_path) | ||||
{ | ||||
int err = 0; | ||||
if (::remove(source_path) != 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to remove source file `%s'", sourc | ||||
e_path); | ||||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* move the regular file 'source_path' to 'target_path'. | * move the regular file 'source_path' to 'target_path'. | |||
*/ | */ | |||
int fm_io_posix::move_file(const ft_string & source_path, const ft_stat & stat, const ft_string & target_path) | int fm_io_posix::move_file(const ft_string & source_path, const ft_stat & stat, const ft_string & target_path) | |||
{ | { | |||
const char * source = source_path.c_str(), * target = target_path.c_str(); | const char * source = source_path.c_str(), * target = target_path.c_str(); | |||
int err = 0; | int err = 0; | |||
skipping to change at line 446 | skipping to change at line 474 | |||
if (simulate_run()) | if (simulate_run()) | |||
return err; | return err; | |||
/* check inode_cache for hard links and recreate them */ | /* check inode_cache for hard links and recreate them */ | |||
err = this->hard_link(stat, target_path); | err = this->hard_link(stat, target_path); | |||
if (err == 0) { | if (err == 0) { | |||
/** hard link succeeded, no need to copy the file contents */ | /** hard link succeeded, no need to copy the file contents */ | |||
err = this->periodic_check_free_space(); | err = this->periodic_check_free_space(); | |||
goto move_file_remove_source; | goto move_file_remove_source; | |||
} else if (err == EAGAIN) { | } else if (err != EAGAIN) { | |||
/* no luck with inode_cache, proceed as usual */ | ||||
err = 0; | ||||
} else { | ||||
/** hard link failed */ | /** hard link failed */ | |||
return err; | return err; | |||
} | } | |||
{ | /* no luck with inode_cache, proceed as usual */ | |||
int in_fd = ::open(source, O_RDWR); | err = copy_file_contents(source_path, stat, target_path); | |||
if (in_fd < 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to open source file `%s'", sou | move_file_remove_source: | |||
rce); | if (err == 0) | |||
err = remove_file(source); | ||||
return err; | ||||
} | ||||
/** | ||||
* copy the contents of regular file 'source_path' to 'target_path'. | ||||
*/ | ||||
int fm_io_posix::copy_file_contents(const ft_string & source_path, const ft_stat | ||||
& stat, const ft_string & target_path) | ||||
{ | ||||
const char * source = source_path.c_str(), * target = target_path.c_str(); | ||||
int err = 0; | ||||
int in_fd = ::open(source, O_RDWR); | ||||
if (in_fd < 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to open source file `%s'", source) | ||||
; | ||||
#ifndef O_EXCL | #ifndef O_EXCL | |||
# define O_EXCL 0 | # define O_EXCL 0 | |||
#endif | #endif | |||
int out_fd = ::open(target, O_CREAT|O_WRONLY|O_TRUNC|O_EXCL, 0600); | int out_fd = ::open(target, O_CREAT|O_WRONLY|O_TRUNC|O_EXCL, 0600); | |||
if (out_fd < 0) | if (out_fd < 0) | |||
err = ff_log(FC_ERROR, errno, "failed to create target file `%s'", t | err = ff_log(FC_ERROR, errno, "failed to create target file `%s'", targe | |||
arget); | t); | |||
if (err == 0) { | if (err == 0) { | |||
err = this->periodic_check_free_space(); | err = this->periodic_check_free_space(); | |||
if (err == 0) | if (err == 0) | |||
err = this->copy_stream(in_fd, out_fd, stat, source, target); | err = this->copy_stream(in_fd, out_fd, stat, source, target); | |||
} | ||||
if (in_fd >= 0) | ||||
(void) ::close(in_fd); | ||||
if (out_fd >= 0) | ||||
(void) ::close(out_fd); | ||||
} | } | |||
if (in_fd >= 0) | ||||
(void) ::close(in_fd); | ||||
if (out_fd >= 0) | ||||
(void) ::close(out_fd); | ||||
if (err == 0) | if (err == 0) | |||
err = this->copy_stat(target, stat); | err = this->copy_stat(target, stat); | |||
move_file_remove_source: | ||||
if (err == 0) { | ||||
if (::remove(source) != 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to remove source file `%s'", s | ||||
ource); | ||||
} | ||||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* try to rename a file, directory or special-device from 'source_path' to 'targ et_path'. | * try to rename a file, directory or special-device from 'source_path' to 'targ et_path'. | |||
*/ | */ | |||
int fm_io_posix::move_rename(const char * source, const char * target) | int fm_io_posix::move_rename(const char * source, const char * target) | |||
{ | { | |||
int err = 0; | int err = 0; | |||
do { | do { | |||
skipping to change at line 513 | skipping to change at line 548 | |||
ff_log(FC_TRACE, 0, "move_rename() `%s'\t-> `%s': success", source, tar get); | ff_log(FC_TRACE, 0, "move_rename() `%s'\t-> `%s': success", source, tar get); | |||
} while (0); | } while (0); | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* check inode_cache for hard links and recreate them. | * check inode_cache for hard links and recreate them. | |||
* must be called if and only if stat.st_nlink > 1 | * must be called if and only if stat.st_nlink > 1 | |||
* | * | |||
* returns EAGAIN if inode was not in inode_cache | * returns EAGAIN if inode *was* not in inode_cache | |||
*/ | */ | |||
int fm_io_posix::hard_link(const ft_stat & stat, const ft_string & target_path) | int fm_io_posix::hard_link(const ft_stat & stat, const ft_string & target_path) | |||
{ | { | |||
const ft_string * cached_link, * to_erase = NULL; | ft_string cached_link = target_path; | |||
int err; | ||||
if (stat.st_nlink > 1) | if (stat.st_nlink > 1) { | |||
/* | /* | |||
* source path has 2 or more links. | * source path has 2 or more links. | |||
* check if it is cached already, or add it to detect further links to t he same file/device | * check if it is cached already, or add it to detect further links to t he same file/device | |||
*/ | */ | |||
cached_link = inode_cache_find_or_add(stat.st_ino, target_path); | err = inode_cache_find_or_add(stat.st_ino, cached_link); | |||
else | } else { | |||
/* | /* | |||
* source path has only 1 link. it can be either: | * source path has only 1 link. it can be either: | |||
* a) the last link of a file/device which previously had multiple links | * a) the last link of a file/device which previously had multiple links | |||
, but we removed them during fm_io_posix::move() | , | |||
* but we all other links during fm_io_posix::move() | ||||
* b) a file/device which always had one link | * b) a file/device which always had one link | |||
* | * | |||
* so we check for its presence in inode_cache, but we do not add it to inode_cache | * so we check for its presence in inode_cache, but we do not add it to inode_cache | |||
* in any case, if a cached inode is found, we will erase it below with inode_cache_erase() | * in any case, if a cached inode is found, we erase it | |||
* because it is guaranteed that no more links to this inode will ever b e found. | * because it is guaranteed that no more links to this inode will ever b e found. | |||
*/ | */ | |||
cached_link = to_erase = inode_cache_find(stat.st_ino, target_path); | err = inode_cache_find_and_delete(stat.st_ino, cached_link); | |||
} | ||||
int err = 0; | ||||
do { | ||||
if (cached_link == NULL) { | ||||
// fake error to tell caller that inode was not in inode_cache | ||||
err = EAGAIN; | ||||
break; | ||||
} | ||||
const char * link_to = cached_link->c_str(), * link_from = target_path.c | if (err == 0) { | |||
_str(); | // fake error to tell caller that inode was not in cache | |||
if (::link(link_to, link_from) != 0) { | err = EAGAIN; | |||
} | ||||
else if (err == 1) { | ||||
// inode found in cache | ||||
const char * link_to = cached_link.c_str(), * link_from = target_path.c_ | ||||
str(); | ||||
if (::link(link_to, link_from) != 0) | ||||
err = ff_log(FC_ERROR, errno, "failed to create target hard link `%s '\t-> `%s'", link_from, link_to); | err = ff_log(FC_ERROR, errno, "failed to create target hard link `%s '\t-> `%s'", link_from, link_to); | |||
break; | else | |||
} | err = 0; | |||
} | ||||
} while (0); | ||||
if (to_erase != NULL) | ||||
inode_cache_erase(stat.st_ino); | ||||
return err; | return err; | |||
} | } | |||
enum { | enum { | |||
FT_LOG_BUFSIZE = 16, //< log2(FT_BUFSIZE) | FT_LOG_BUFSIZE = 16, //< log2(FT_BUFSIZE) | |||
// FT_BUFSIZE must be a power of 2 (currently 64k), | // FT_BUFSIZE must be a power of 2 (currently 64k), | |||
// and must be somewhat smaller than fm_disk_stat::THRESHOLD_MIN (currently 96k) | // and must be somewhat smaller than fm_disk_stat::THRESHOLD_MIN (currently 96k) | |||
FT_BUFSIZE = (ft_size)1 << FT_LOG_BUFSIZE, | FT_BUFSIZE = (ft_size)1 << FT_LOG_BUFSIZE, | |||
skipping to change at line 604 | skipping to change at line 635 | |||
target, pretty_size, pretty_label); | target, pretty_size, pretty_label); | |||
if (::lseek(in_fd, 0, SEEK_END) != file_size) | if (::lseek(in_fd, 0, SEEK_END) != file_size) | |||
return ff_log(FC_ERROR, errno, "error seeking to end of file `%s'", sour ce); | return ff_log(FC_ERROR, errno, "error seeking to end of file `%s'", sour ce); | |||
ft_off offset_high = file_size, offset_low; | ft_off offset_high = file_size, offset_low; | |||
if ((err = fd_truncate(out_fd, offset_high, target)) != 0) | if ((err = fd_truncate(out_fd, offset_high, target)) != 0) | |||
return err; | return err; | |||
::sync(); // slow, but on Linux not doing it is worse | // slow, but on Linux not doing it is worse: | |||
// you can get inaccurate disk usage statistics | ||||
// and (if loop device becomes full) silent I/O errors! | ||||
sync(); | ||||
char buf[FT_BUFSIZE]; | char buf[FT_BUFSIZE]; | |||
ft_size expected, got; | ft_size expected, got; | |||
ft_size hole_len, nonhole_len, tosend_offset, tosend_left; | ft_size hole_len, nonhole_len, tosend_offset, tosend_left; | |||
while (offset_high > 0) { | while (offset_high > 0) { | |||
/** truncate in_fd, discarding any data that we already copied */ | /** truncate in_fd, discarding any data that we already copied */ | |||
if ((err = fd_truncate(in_fd, offset_high, source)) != 0) | if ((err = fd_truncate(in_fd, offset_high, source)) != 0) | |||
break; | break; | |||
offset_low = (offset_high - 1) & ~(ft_off)FT_BUFSIZE_m1; | offset_low = (offset_high - 1) & ~(ft_off)FT_BUFSIZE_m1; | |||
ff_assert(offset_high - offset_low <= FT_BUFSIZE); | ff_assert(offset_high - offset_low <= FT_BUFSIZE); | |||
got = expected = (ft_size)(offset_high - offset_low); | got = expected = (ft_size)(offset_high - offset_low); | |||
if ((err = fd_seek2(in_fd, out_fd, offset_low, source, target)) != 0) | if ((err = fd_seek2(in_fd, out_fd, offset_low, source, target)) != 0) | |||
break; | break; | |||
if ((err = this->full_read(in_fd, buf, got, source)) != 0 || got != expe cted) { | if ((err = this->full_read(in_fd, buf, got, source)) != 0 || got != expe cted) { | |||
if (err == 0) { | if (err == 0) { | |||
ff_log(FC_ERROR, 0, "error reading from `%s': expected %"FT_ULL" bytes, got %"FT_ULL" bytes", | ff_log(FC_ERROR, 0, "error reading from `%s': expected %" FT_ULL " bytes, got %" FT_ULL " bytes", | |||
source, (ft_ull)expected, (ft_ull)got); | source, (ft_ull)expected, (ft_ull)got); | |||
err = -EIO; | err = -EIO; | |||
} | } | |||
break; | break; | |||
} | } | |||
tosend_offset = 0; | tosend_offset = 0; | |||
for (tosend_left = got; tosend_left != 0; ) { | for (tosend_left = got; tosend_left != 0; ) { | |||
/* detect hole */ | /* detect hole */ | |||
if ((hole_len = hole_length(buf + tosend_offset, tosend_left)) != 0) { | if ((hole_len = hole_length(buf + tosend_offset, tosend_left)) != 0) { | |||
/* re-create hole in target file */ | /* re-create hole in target file */ | |||
if (::lseek(out_fd, (ft_off)hole_len, SEEK_CUR) == (ft_off)-1) { | if (::lseek(out_fd, (ft_off)hole_len, SEEK_CUR) == (ft_off)-1) { | |||
err = ff_log(FC_ERROR, errno, "error seeking %"FT_ULL" bytes forward in file `%s'", (ft_ull)hole_len, target); | err = ff_log(FC_ERROR, errno, "error seeking %" FT_ULL " byt es forward in file `%s'", (ft_ull)hole_len, target); | |||
break; | break; | |||
} | } | |||
tosend_offset += hole_len; | tosend_offset += hole_len; | |||
tosend_left -= hole_len; | tosend_left -= hole_len; | |||
} | } | |||
if ((nonhole_len = nonhole_length(buf + tosend_offset, tosend_left)) != 0) { | if ((nonhole_len = nonhole_length(buf + tosend_offset, tosend_left)) != 0) { | |||
// copy the non-hole data | // copy the non-hole data | |||
if ((err = this->full_write(out_fd, buf + tosend_offset, nonhole _len, target)) != 0) | if ((err = this->full_write(out_fd, buf + tosend_offset, nonhole _len, target)) != 0) | |||
break; | break; | |||
skipping to change at line 663 | skipping to change at line 697 | |||
break; | break; | |||
offset_high = offset_low; | offset_high = offset_low; | |||
} | } | |||
if (err != 0 && offset_high != 0 && offset_high != file_size) { | if (err != 0 && offset_high != 0 && offset_high != file_size) { | |||
ff_log(FC_ERROR, 0, "DANGER! due to previous error, copying `%s' -> `%s' was aborted", source, target); | ff_log(FC_ERROR, 0, "DANGER! due to previous error, copying `%s' -> `%s' was aborted", source, target); | |||
ff_log(FC_ERROR, 0, " and BOTH copies of this file are now incomp lete."); | ff_log(FC_ERROR, 0, " and BOTH copies of this file are now incomp lete."); | |||
ff_log(FC_ERROR, 0, " To recover this file, execute the following command"); | ff_log(FC_ERROR, 0, " To recover this file, execute the following command"); | |||
ff_log(FC_ERROR, 0, " AFTER freeing enough space in the source de vice:"); | ff_log(FC_ERROR, 0, " AFTER freeing enough space in the source de vice:"); | |||
offset_high >>= FT_LOG_BUFSIZE; | offset_high >>= FT_LOG_BUFSIZE; | |||
ff_log(FC_ERROR, 0, " /bin/dd bs=%"FT_ULL" skip=%"FT_ULL" seek= %"FT_ULL" conv=notrunc if=\"%s\" of=\"%s\"", | ff_log(FC_ERROR, 0, " /bin/dd bs=%" FT_ULL " skip=%" FT_ULL " s eek=%" FT_ULL " conv=notrunc if=\"%s\" of=\"%s\"", | |||
(ft_ull)FT_BUFSIZE, (ft_ull)offset_high, (ft_ull)offset_high, ta rget, source); | (ft_ull)FT_BUFSIZE, (ft_ull)offset_high, (ft_ull)offset_high, ta rget, source); | |||
} | } | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* forward copy file/stream contents from in_fd to out_fd. | * forward copy file/stream contents from in_fd to out_fd. | |||
*/ | */ | |||
int fm_io_posix::copy_stream_forward(int in_fd, int out_fd, const char * source, const char * target) | int fm_io_posix::copy_stream_forward(int in_fd, int out_fd, const char * source, const char * target) | |||
{ | { | |||
skipping to change at line 744 | skipping to change at line 778 | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* truncate file pointed by descriptor to specified length | * truncate file pointed by descriptor to specified length | |||
*/ | */ | |||
int fm_io_posix::fd_truncate(int fd, ft_off length, const char * path) | int fm_io_posix::fd_truncate(int fd, ft_off length, const char * path) | |||
{ | { | |||
int err = 0; | int err = 0; | |||
if (::ftruncate(fd, length) == -1) | if (::ftruncate(fd, length) == -1) | |||
err = ff_log(FC_ERROR, errno, "error truncating file `%s' to %"FT_ULL" b ytes", path, (ft_ull)length); | err = ff_log(FC_ERROR, errno, "error truncating file `%s' to %" FT_ULL " bytes", path, (ft_ull)length); | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* seek to specified position of *both* fd1 and fd2 | * seek to specified position of *both* fd1 and fd2 | |||
*/ | */ | |||
int fm_io_posix::fd_seek2(int fd1, int fd2, ft_off offset, const char * path1, c onst char * path2) | int fm_io_posix::fd_seek2(int fd1, int fd2, ft_off offset, const char * path1, c onst char * path2) | |||
{ | { | |||
int err = fd_seek(fd1, offset, path1); | int err = fd_seek(fd1, offset, path1); | |||
if (err == 0) | if (err == 0) | |||
skipping to change at line 766 | skipping to change at line 800 | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* seek to specified position of file descriptor | * seek to specified position of file descriptor | |||
*/ | */ | |||
int fm_io_posix::fd_seek(int fd, ft_off offset, const char * path) | int fm_io_posix::fd_seek(int fd, ft_off offset, const char * path) | |||
{ | { | |||
int err = 0; | int err = 0; | |||
if (::lseek(fd, offset, SEEK_SET) != offset) | if (::lseek(fd, offset, SEEK_SET) != offset) | |||
err = ff_log(FC_ERROR, errno, "error seeking to position %"FT_ULL" of fi le `%s'", (ft_ull)offset, path); | err = ff_log(FC_ERROR, errno, "error seeking to position %" FT_ULL " of file `%s'", (ft_ull)offset, path); | |||
return err; | return err; | |||
} | } | |||
/** | /** | |||
* scan memory for blocksize-length and blocksize-aligned sections full of zeroe s | * scan memory for blocksize-length and blocksize-aligned sections full of zeroe s | |||
* return length of zeroed area at the beginning of scanned memory. | * return length of zeroed area at the beginning of scanned memory. | |||
* returned length is rounded down to block_size | * returned length is rounded down to block_size | |||
*/ | */ | |||
size_t fm_io_posix::hole_length(const char * mem, ft_size mem_len) | size_t fm_io_posix::hole_length(const char * mem, ft_size mem_len) | |||
{ | { | |||
skipping to change at line 919 | skipping to change at line 953 | |||
/* copy owner and group. this resets any SUID bits */ | /* copy owner and group. this resets any SUID bits */ | |||
#if defined(FT_HAVE_LCHOWN) | #if defined(FT_HAVE_LCHOWN) | |||
if (lchown(target, stat.st_uid, stat.st_gid) != 0) | if (lchown(target, stat.st_uid, stat.st_gid) != 0) | |||
#else | #else | |||
if (!is_symlink && chown(target, stat.st_uid, stat.st_gid) != 0) | if (!is_symlink && chown(target, stat.st_uid, stat.st_gid) != 0) | |||
#endif | #endif | |||
{ | { | |||
err = ff_log(is_error ? FC_ERROR : FC_WARN, errno, | err = ff_log(is_error ? FC_ERROR : FC_WARN, errno, | |||
"%s set owner=%"FT_ULL" and group=%"FT_ULL" on %s `%s'", | "%s set owner=%" FT_ULL " and group=%" FT_ULL " on %s `%s'", | |||
fail_label, (ft_ull)stat.st_uid, (ft_ull)stat.st_gid, label, target); | fail_label, (ft_ull)stat.st_uid, (ft_ull)stat.st_gid, label, target); | |||
if (is_error) | if (is_error) | |||
break; | break; | |||
err = 0; | err = 0; | |||
} | } | |||
/* | /* | |||
* copy permission bits | * copy permission bits | |||
* 1. chmod() on a symbolic link has no sense, don't to it | * 1. chmod() on a symbolic link has no sense, don't to it | |||
* 2. chmod() must be performed AFTER lchown(), because lchown() resets any SUID bits | * 2. chmod() must be performed AFTER lchown(), because lchown() resets any SUID bits | |||
*/ | */ | |||
if (!is_symlink && chmod(target, stat.st_mode) != 0) { | if (!is_symlink && chmod(target, stat.st_mode) != 0) { | |||
err = ff_log(is_error ? FC_ERROR : FC_WARN, errno, | err = ff_log(is_error ? FC_ERROR : FC_WARN, errno, | |||
"%s change mode to 0%"FT_OLL" on %s `%s'", | "%s change mode to 0%" FT_OLL " on %s `%s'", | |||
fail_label, (ft_ull)stat.st_mode, label, target); | fail_label, (ft_ull)stat.st_mode, label, target); | |||
if (is_error) | if (is_error) | |||
break; | break; | |||
err = 0; | err = 0; | |||
} | } | |||
} while (0); | } while (0); | |||
return err; | return err; | |||
} | } | |||
skipping to change at line 1002 | skipping to change at line 1036 | |||
*/ | */ | |||
int fm_io_posix::remove_dir(const ft_string & path) | int fm_io_posix::remove_dir(const ft_string & path) | |||
{ | { | |||
const char * dir = path.c_str(); | const char * dir = path.c_str(); | |||
int err = 0; | int err = 0; | |||
ff_log(FC_TRACE, 0, "remove_dir() `%s'", dir); | ff_log(FC_TRACE, 0, "remove_dir() `%s'", dir); | |||
do { | do { | |||
if (simulate_run() || is_source_lost_found(path)) | if (simulate_run() || is_source_lost_found(path)) | |||
break; | break; | |||
if (remove(dir) != 0) { | if (::remove(dir) != 0) { | |||
/* ignore error if we are removing source root: it is allowed to be in use */ | /* ignore error if we are removing source root: it is allowed to be in use */ | |||
if (path != source_root()) { | if (path != source_root()) { | |||
/* if force_run(), failure to remove a source directory is just a warning */ | /* if force_run(), failure to remove a source directory is just a warning */ | |||
bool is_warn = force_run(); | bool is_warn = force_run(); | |||
err = ff_log(is_warn ? FC_WARN : FC_ERROR, errno, "%sfailed to r emove source directory `%s'", is_warn ? "warning: " : "", dir); | err = ff_log(is_warn ? FC_WARN : FC_ERROR, errno, "%sfailed to r emove source directory `%s'", is_warn ? "warning: " : "", dir); | |||
if (!is_warn) | if (!is_warn) | |||
break; | break; | |||
err = 0; | err = 0; | |||
} | } | |||
break; | break; | |||
End of changes. 39 change blocks. | ||||
80 lines changed or deleted | 116 lines changed or added |