/*
Copyright (C) 2010 ABRT team
Copyright (C) 2010 RedHat Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Utility routines.
*
*/
#include "internal_libreport.h"
#define CONFIG_FEATURE_COPYBUF_KB 4
static const char msg_write_error[] = "write error";
static const char msg_read_error[] = "read error";
static off_t full_fd_action(int src_fd, int dst_fd, off_t size, int flags)
{
int status = -1;
off_t total = 0;
int last_was_seek = 0;
#if CONFIG_FEATURE_COPYBUF_KB <= 4
char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024];
enum { buffer_size = sizeof(buffer) };
#else
char *buffer;
int buffer_size;
/* We want page-aligned buffer, just in case kernel is clever
* and can do page-aligned io more efficiently */
buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
/* ignored: */ -1, 0);
buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024;
if (buffer == MAP_FAILED) {
buffer = alloca(4 * 1024);
buffer_size = 4 * 1024;
}
#endif
if (src_fd < 0)
goto out;
if (!size) {
size = buffer_size;
status = 1; /* copy until eof */
}
while (1) {
ssize_t rd, towrite;
rd = safe_read(src_fd, buffer, buffer_size);
if (!rd) { /* eof - all done */
if (last_was_seek) {
if (lseek(dst_fd, -1, SEEK_CUR) < 0
|| safe_write(dst_fd, "", 1) != 1
) {
perror_msg("%s", msg_write_error);
break;
}
}
status = 0;
break;
}
if (rd < 0) {
perror_msg("%s", msg_read_error);
break;
}
/* Add read Bytes before quiting the loop, because the caller
* needs to be able to detect overflows (the return value > size). */
total += rd;
towrite = rd > size ? size : rd;
if (towrite == 0) {
/* no more Bytes to write - all done */
status = 0;
break;
}
/* dst_fd == -1 is a fake, else... */
if (dst_fd >= 0) {
if (flags & COPYFD_SPARSE) {
ssize_t cnt = towrite;
while (--cnt >= 0)
if (buffer[cnt] != 0)
goto need2write;
if (lseek(dst_fd, towrite, SEEK_CUR) < 0) {
flags &= ~COPYFD_SPARSE;
goto need2write;
}
last_was_seek = 1;
} else {
need2write:
{
ssize_t wr = full_write(dst_fd, buffer, towrite);
if (wr < towrite) {
perror_msg("%s", msg_write_error);
break;
}
last_was_seek = 0;
}
}
}
if (status < 0) { /* if we aren't copying till EOF... */
size -= towrite;
}
}
out:
#if CONFIG_FEATURE_COPYBUF_KB > 4
if (buffer_size != 4 * 1024)
munmap(buffer, buffer_size);
#endif
return status ? -1 : total;
}
off_t copyfd_ext_at(int src, int dir_fd, const char *name, int mode, uid_t uid, gid_t gid, int open_flags, int copy_flags, off_t size)
{
int dst = openat(dir_fd, name, open_flags, mode);
if (dst < 0)
{
perror_msg("Can't open '%s'", name);
return -1;
}
off_t r = full_fd_action(src, dst, size, copy_flags);
if (uid != (uid_t)-1L)
{
if (fchown(dst, uid, gid) == -1)
{
r = -2;
perror_msg("Can't change ownership of '%s' to %lu:%lu", name, (long)uid, (long)gid);
}
}
close(dst);
return r;
}
off_t copyfd_size(int fd1, int fd2, off_t size, int flags)
{
if (size) {
off_t read = full_fd_action(fd1, fd2, size, flags);
/* full_fd_action() writes only up to the size Bytes but returns the
* number of read Bytes. Callers of this function expect
* the return value not being greater then the size argument. */
return read > size ? size : read;
}
return 0;
}
void copyfd_exact_size(int fd1, int fd2, off_t size)
{
off_t sz = copyfd_size(fd1, fd2, size, /*flags:*/ 0);
if (sz == size)
return;
if (sz != -1)
error_msg_and_die("short read");
/* if sz == -1, copyfd_XX already complained */
xfunc_die();
}
off_t copyfd_eof(int fd1, int fd2, int flags)
{
return full_fd_action(fd1, fd2, 0, flags);
}
off_t copy_file_ext_2at(int src_dir_fd, const char *src_name, int dir_fd, const char *name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags)
{
off_t r;
int src = openat(src_dir_fd, src_name, src_flags);
if (src < 0)
{
perror_msg("Can't open '%s'", src_name);
return -1;
}
r = copyfd_ext_at(src, dir_fd, name, mode, uid, gid, dst_flags,
/*copy flags*/0,
/*read all data*/0);
close(src);
return r;
}
off_t copy_file_ext_at(const char *src_name, int dir_fd, const char *name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags)
{
off_t r;
int src = open(src_name, src_flags);
if (src < 0)
{
perror_msg("Can't open '%s'", src_name);
return -1;
}
r = copyfd_ext_at(src, dir_fd, name, mode, uid, gid, dst_flags,
/*copy flags*/0,
/*read all data*/0);
close(src);
return r;
}
off_t copy_file_at(const char *src_name, int dir_fd, const char *name, int mode)
{
return copy_file_ext_at(src_name, dir_fd, name, mode, -1, -1,
O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT);
}
off_t copy_file(const char *src_name, const char *dst_name, int mode)
{
return copy_file_ext(src_name, dst_name, mode, -1, -1,
O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT);
}