Skip to content

Instantly share code, notes, and snippets.

@ThiefMaster
Created September 6, 2012 11:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ThiefMaster/3655373 to your computer and use it in GitHub Desktop.
Save ThiefMaster/3655373 to your computer and use it in GitHub Desktop.
copy
/* Copy a regular file from SRC_NAME to DST_NAME.
If the source file contains holes, copies holes and blocks of zeros
in the source file as holes in the destination file.
(Holes are read as zeroes by the `read' system call.)
When creating the destination, use DST_MODE & ~OMITTED_PERMISSIONS
as the third argument in the call to open, adding
OMITTED_PERMISSIONS after copying as needed.
X provides many option settings.
Return true if successful.
*NEW_DST is as in copy_internal.
SRC_SB is the result of calling XSTAT (aka stat) on SRC_NAME. */
static bool
copy_reg (char const *src_name, char const *dst_name,
const struct cp_options *x,
mode_t dst_mode, mode_t omitted_permissions, bool *new_dst,
struct stat const *src_sb)
{
char *buf;
char *buf_alloc = NULL;
char *name_alloc = NULL;
int dest_desc;
int dest_errno;
int source_desc;
mode_t src_mode = src_sb->st_mode;
struct stat sb;
struct stat src_open_sb;
bool return_val = true;
bool data_copy_required = true;
source_desc = open (src_name,
(O_RDONLY | O_BINARY
| (x->dereference == DEREF_NEVER ? O_NOFOLLOW : 0)));
if (source_desc < 0)
{
error (0, errno, _("cannot open %s for reading"), quote (src_name));
return false;
}
if (fstat (source_desc, &src_open_sb) != 0)
{
error (0, errno, _("cannot fstat %s"), quote (src_name));
return_val = false;
goto close_src_desc;
}
/* Compare the source dev/ino from the open file to the incoming,
saved ones obtained via a previous call to stat. */
if (! SAME_INODE (*src_sb, src_open_sb))
{
error (0, 0,
_("skipping file %s, as it was replaced while being copied"),
quote (src_name));
return_val = false;
goto close_src_desc;
}
/* The semantics of the following open calls are mandated
by the specs for both cp and mv. */
if (! *new_dst)
{
dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY);
dest_errno = errno;
/* When using cp --preserve=context to copy to an existing destination,
use the default context rather than that of the source. Why?
1) the src context may prohibit writing, and
2) because it's more consistent to use the same context
that is used when the destination file doesn't already exist. */
if (x->preserve_security_context && 0 <= dest_desc)
{
security_context_t con = NULL;
if (getfscreatecon (&con) < 0)
{
if (x->require_preserve_context ||
(!x->reduce_diagnostics && !errno_unsupported (errno)))
error (0, errno, _("failed to get file system create context"));
if (x->require_preserve_context)
{
return_val = false;
goto close_src_and_dst_desc;
}
}
if (con)
{
if (fsetfilecon (dest_desc, con) < 0)
{
if (x->require_preserve_context ||
(!x->reduce_diagnostics && !errno_unsupported (errno)))
error (0, errno,
_("failed to set the security context of %s to %s"),
quote_n (0, dst_name), quote_n (1, con));
if (x->require_preserve_context)
{
return_val = false;
freecon (con);
goto close_src_and_dst_desc;
}
}
freecon (con);
}
}
if (dest_desc < 0 && x->unlink_dest_after_failed_open)
{
if (unlink (dst_name) != 0)
{
error (0, errno, _("cannot remove %s"), quote (dst_name));
return_val = false;
goto close_src_desc;
}
if (x->verbose)
printf (_("removed %s\n"), quote (dst_name));
/* Tell caller that the destination file was unlinked. */
*new_dst = true;
}
}
if (*new_dst)
{
int open_flags = O_WRONLY | O_CREAT | O_BINARY;
dest_desc = open (dst_name, open_flags | O_EXCL,
dst_mode & ~omitted_permissions);
dest_errno = errno;
/* When trying to copy through a dangling destination symlink,
the above open fails with EEXIST. If that happens, and
lstat'ing the DST_NAME shows that it is a symlink, then we
have a problem: trying to resolve this dangling symlink to
a directory/destination-entry pair is fundamentally racy,
so punt. If x->open_dangling_dest_symlink is set (cp sets
that when POSIXLY_CORRECT is set in the environment), simply
call open again, but without O_EXCL (potentially dangerous).
If not, fail with a diagnostic. These shenanigans are necessary
only when copying, i.e., not in move_mode. */
if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode)
{
struct stat dangling_link_sb;
if (lstat (dst_name, &dangling_link_sb) == 0
&& S_ISLNK (dangling_link_sb.st_mode))
{
if (x->open_dangling_dest_symlink)
{
dest_desc = open (dst_name, open_flags,
dst_mode & ~omitted_permissions);
dest_errno = errno;
}
else
{
error (0, 0, _("not writing through dangling symlink %s"),
quote (dst_name));
return_val = false;
goto close_src_desc;
}
}
}
}
else
omitted_permissions = 0;
if (dest_desc < 0)
{
error (0, dest_errno, _("cannot create regular file %s"),
quote (dst_name));
return_val = false;
goto close_src_desc;
}
if (fstat (dest_desc, &sb) != 0)
{
error (0, errno, _("cannot fstat %s"), quote (dst_name));
return_val = false;
goto close_src_and_dst_desc;
}
if (x->reflink_mode)
{
bool clone_ok = clone_file (dest_desc, source_desc) == 0;
if (clone_ok || x->reflink_mode == REFLINK_ALWAYS)
{
if (!clone_ok)
{
error (0, errno, _("failed to clone %s"), quote (dst_name));
return_val = false;
goto close_src_and_dst_desc;
}
data_copy_required = false;
}
}
if (data_copy_required)
{
typedef uintptr_t word;
off_t n_read_total = 0;
/* Choose a suitable buffer size; it may be adjusted later. */
size_t buf_alignment = lcm (getpagesize (), sizeof (word));
size_t buf_alignment_slop = sizeof (word) + buf_alignment - 1;
size_t buf_size = io_blksize (sb);
/* Deal with sparse files. */
bool last_write_made_hole = false;
bool make_holes = false;
if (S_ISREG (sb.st_mode))
{
/* Even with --sparse=always, try to create holes only
if the destination is a regular file. */
if (x->sparse_mode == SPARSE_ALWAYS)
make_holes = true;
#if HAVE_STRUCT_STAT_ST_BLOCKS
/* Use a heuristic to determine whether SRC_NAME contains any sparse
blocks. If the file has fewer blocks than would normally be
needed for a file of its size, then at least one of the blocks in
the file is a hole. */
if (x->sparse_mode == SPARSE_AUTO && S_ISREG (src_open_sb.st_mode)
&& ST_NBLOCKS (src_open_sb) < src_open_sb.st_size / ST_NBLOCKSIZE)
make_holes = true;
#endif
}
/* If not making a sparse file, try to use a more-efficient
buffer size. */
if (! make_holes)
{
/* Compute the least common multiple of the input and output
buffer sizes, adjusting for outlandish values. */
size_t blcm_max = MIN (SIZE_MAX, SSIZE_MAX) - buf_alignment_slop;
size_t blcm = buffer_lcm (io_blksize (src_open_sb), buf_size,
blcm_max);
/* Do not bother with a buffer larger than the input file, plus one
byte to make sure the file has not grown while reading it. */
if (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size < buf_size)
buf_size = src_open_sb.st_size + 1;
/* However, stick with a block size that is a positive multiple of
blcm, overriding the above adjustments. Watch out for
overflow. */
buf_size += blcm - 1;
buf_size -= buf_size % blcm;
if (buf_size == 0 || blcm_max < buf_size)
buf_size = blcm;
}
/* Make a buffer with space for a sentinel at the end. */
buf_alloc = xmalloc (buf_size + buf_alignment_slop);
buf = ptr_align (buf_alloc, buf_alignment);
for (;;)
{
word *wp = NULL;
ssize_t n_read = read (source_desc, buf, buf_size);
if (n_read < 0)
{
#ifdef EINTR
if (errno == EINTR)
continue;
#endif
error (0, errno, _("reading %s"), quote (src_name));
return_val = false;
goto close_src_and_dst_desc;
}
if (n_read == 0)
break;
n_read_total += n_read;
if (make_holes)
{
char *cp;
/* Sentinel to stop loop. */
buf[n_read] = '\1';
#ifdef lint
/* Usually, buf[n_read] is not the byte just before a "word"
(aka uintptr_t) boundary. In that case, the word-oriented
test below (*wp++ == 0) would read some uninitialized bytes
after the sentinel. To avoid false-positive reports about
this condition (e.g., from a tool like valgrind), set the
remaining bytes -- to any value. */
memset (buf + n_read + 1, 0, sizeof (word) - 1);
#endif
/* Find first nonzero *word*, or the word with the sentinel. */
wp = (word *) buf;
while (*wp++ == 0)
continue;
/* Find the first nonzero *byte*, or the sentinel. */
cp = (char *) (wp - 1);
while (*cp++ == 0)
continue;
if (cp <= buf + n_read)
/* Clear to indicate that a normal write is needed. */
wp = NULL;
else
{
/* We found the sentinel, so the whole input block was zero.
Make a hole. */
if (lseek (dest_desc, n_read, SEEK_CUR) < 0)
{
error (0, errno, _("cannot lseek %s"), quote (dst_name));
return_val = false;
goto close_src_and_dst_desc;
}
last_write_made_hole = true;
}
}
if (!wp)
{
size_t n = n_read;
if (full_write (dest_desc, buf, n) != n)
{
error (0, errno, _("writing %s"), quote (dst_name));
return_val = false;
goto close_src_and_dst_desc;
}
last_write_made_hole = false;
/* It is tempting to return early here upon a short read from a
regular file. That would save the final read syscall for each
file. Unfortunately that doesn't work for certain files in
/proc with linux kernels from at least 2.6.9 .. 2.6.29. */
}
}
/* If the file ends with a `hole', we need to do something to record
the length of the file. On modern systems, calling ftruncate does
the job. On systems without native ftruncate support, we have to
write a byte at the ending position. Otherwise the kernel would
truncate the file at the end of the last write operation. */
if (last_write_made_hole)
{
if (ftruncate (dest_desc, n_read_total) < 0)
{
error (0, errno, _("truncating %s"), quote (dst_name));
return_val = false;
goto close_src_and_dst_desc;
}
}
}
if (x->preserve_timestamps)
{
struct timespec timespec[2];
timespec[0] = get_stat_atime (src_sb);
timespec[1] = get_stat_mtime (src_sb);
if (gl_futimens (dest_desc, dst_name, timespec) != 0)
{
error (0, errno, _("preserving times for %s"), quote (dst_name));
if (x->require_preserve)
{
return_val = false;
goto close_src_and_dst_desc;
}
}
}
/* Set ownership before xattrs as changing owners will
clear capabilities. */
if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb))
{
switch (set_owner (x, dst_name, dest_desc, src_sb, *new_dst, &sb))
{
case -1:
return_val = false;
goto close_src_and_dst_desc;
case 0:
src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX);
break;
}
}
/* To allow copying xattrs on read-only files, temporarily chmod u+rw.
This workaround is required as an inode permission check is done
by xattr_permission() in fs/xattr.c of the GNU/Linux kernel tree. */
if (x->preserve_xattr)
{
bool access_changed = false;
if (!(sb.st_mode & S_IWUSR) && geteuid () != 0)
access_changed = fchmod_or_lchmod (dest_desc, dst_name, 0600) == 0;
if (!copy_attr_by_fd (src_name, source_desc, dst_name, dest_desc, x)
&& x->require_preserve_xattr)
return_val = false;
if (access_changed)
fchmod_or_lchmod (dest_desc, dst_name, dst_mode & ~omitted_permissions);
}
set_author (dst_name, dest_desc, src_sb);
if (x->preserve_mode || x->move_mode)
{
if (copy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0
&& x->require_preserve)
return_val = false;
}
else if (x->set_mode)
{
if (set_acl (dst_name, dest_desc, x->mode) != 0)
return_val = false;
}
else if (omitted_permissions)
{
omitted_permissions &= ~ cached_umask ();
if (omitted_permissions
&& fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0)
{
error (0, errno, _("preserving permissions for %s"),
quote (dst_name));
if (x->require_preserve)
return_val = false;
}
}
close_src_and_dst_desc:
if (close (dest_desc) < 0)
{
error (0, errno, _("closing %s"), quote (dst_name));
return_val = false;
}
close_src_desc:
if (close (source_desc) < 0)
{
error (0, errno, _("closing %s"), quote (src_name));
return_val = false;
}
free (buf_alloc);
free (name_alloc);
return return_val;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment