455 lines
14 KiB
C
455 lines
14 KiB
C
// for copy_file_range()
|
|
#define _GNU_SOURCE
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
#include <Eina.h>
|
|
#include <Ecore_File.h>
|
|
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "status.h"
|
|
#include "fs.h"
|
|
|
|
// generic error handler. special case handling all errnos for everything is
|
|
// pretty insane - so handle non-errors in switches and otherwise pass to
|
|
// this tio handle the reast in a generic way.
|
|
static void
|
|
_error_handle(const char *src, const char *dst, const char *op, int errno_in)
|
|
{
|
|
// XXX: Fix str to be Move, Copy or Delete as it can handle any
|
|
Eina_Strbuf *buf = eina_strbuf_new();
|
|
|
|
if (!buf) abort();
|
|
eina_strbuf_append(buf, op);
|
|
eina_strbuf_append(buf, ": ");
|
|
#define HNDL(_err, _str) \
|
|
_err: \
|
|
eina_strbuf_append(buf, _str); \
|
|
status_error(src, dst, eina_strbuf_string_get(buf)); \
|
|
break
|
|
switch (errno_in)
|
|
{
|
|
HNDL(case EACCES, "Access denied");
|
|
HNDL(case EFAULT, "Memory Fault");
|
|
HNDL(case ELOOP, "Too many symlinks");
|
|
HNDL(case ENAMETOOLONG, "Name too long");
|
|
HNDL(case ENOMEM, "Out of memory");
|
|
HNDL(case ENOTDIR, "Path component is not a directory");
|
|
HNDL(case EOVERFLOW, "Overflow");
|
|
HNDL(case EDQUOT, "Over quota");
|
|
HNDL(case EINVAL, "Invalid value");
|
|
HNDL(case EMLINK, "Too many links");
|
|
HNDL(case ENOENT, "Does not exist");
|
|
HNDL(case ENOSPC, "Disk full");
|
|
HNDL(case EPERM, "Permission denied");
|
|
HNDL(case EROFS, "Read only filesystem");
|
|
HNDL(case EBADF, "Bad file descriptor");
|
|
HNDL(case EIO, "I/O error");
|
|
HNDL(case EISDIR, "Destination is a directory");
|
|
HNDL(case EFBIG, "File too big");
|
|
HNDL(case ETXTBSY, "Text file busy");
|
|
HNDL(case EBUSY, "File busy");
|
|
HNDL(case ENOTEMPTY, "Destination is not empty");
|
|
HNDL(case EEXIST, "File exists");
|
|
HNDL(default, "Unknown error");
|
|
}
|
|
eina_strbuf_free(buf);
|
|
}
|
|
|
|
// this scans a tree to build a potential operation progress count. it may
|
|
// not be 100% right as the fs can change while the scan happens and after
|
|
// so any move that devolves into a cp + rm isn't going to be atomic and
|
|
// handle a changing fs while it works anyway
|
|
Eina_Bool
|
|
fs_scan(const char *src)
|
|
{
|
|
Eina_Bool res = EINA_TRUE;
|
|
Eina_Iterator *it;
|
|
const char *s;
|
|
struct stat st;
|
|
const char *op = "Scan";
|
|
|
|
if (strlen(src) < 1) return EINA_FALSE;
|
|
|
|
if (lstat(src, &st) != 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT: // ignore this error - file removed during scan ?
|
|
return EINA_TRUE;
|
|
default:
|
|
_error_handle(src, NULL, op, errno);
|
|
return EINA_FALSE;
|
|
}
|
|
}
|
|
if (S_ISDIR(st.st_mode))
|
|
{ // it's a dir - scan this recursively
|
|
it = eina_file_ls(src);
|
|
if (it)
|
|
{
|
|
EINA_ITERATOR_FOREACH(it, s)
|
|
{
|
|
if (res)
|
|
{
|
|
if (!fs_scan(s)) res = EINA_FALSE;
|
|
}
|
|
eina_stringshare_del(s);
|
|
}
|
|
eina_iterator_free(it);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// the file itself count as 1 progress item - useful if it's a symlink
|
|
// or a char or block device etc.
|
|
status_count(1, ecore_file_file_get(src));
|
|
// in addition each byte in the file count as a progress item too
|
|
if (st.st_size > 0) status_count(st.st_size, ecore_file_file_get(src));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
Eina_Bool
|
|
fs_cp_rm(const char *src, const char *dst, Eina_Bool report_err, Eina_Bool cp,
|
|
Eina_Bool rm)
|
|
{ // cp_rm /path/to/src/filename /path/to/dst/filename
|
|
Eina_Bool res = EINA_TRUE;
|
|
Eina_Iterator *it;
|
|
const char *s;
|
|
struct stat st;
|
|
mode_t old_umask;
|
|
struct timeval times[2];
|
|
const char *op = "";
|
|
Eina_Strbuf *sbuf = NULL;
|
|
|
|
if (strlen(src) < 1) return EINA_FALSE;
|
|
|
|
// first count how much work needs doing
|
|
if (!fs_scan(src)) return EINA_FALSE;
|
|
|
|
if (rm && cp) op = "Move";
|
|
else if (!rm && cp) op = "Copy";
|
|
else if (rm && !cp) op = "Delete";
|
|
|
|
old_umask = umask(0);
|
|
if (lstat(src, &st) != 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT: // ignore this error - file removed during scan ?
|
|
eina_strbuf_reset(sbuf);
|
|
eina_strbuf_append(sbuf, op);
|
|
eina_strbuf_append(sbuf, ": ");
|
|
eina_strbuf_append(sbuf, "File vanished");
|
|
status_pos(1, eina_strbuf_string_get(sbuf));
|
|
goto err;
|
|
default:
|
|
_error_handle(src, dst, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err;
|
|
}
|
|
}
|
|
sbuf = eina_strbuf_new();
|
|
if (S_ISDIR(st.st_mode))
|
|
{ // it's a dir - scan this recursively
|
|
if (cp)
|
|
{
|
|
if (mkdir(dst, st.st_mode) != 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EEXIST: // ignore - mv would mv over this anyway
|
|
break;
|
|
default: // WAT
|
|
_error_handle(NULL, dst, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
it = eina_file_ls(src);
|
|
if (it)
|
|
{
|
|
EINA_ITERATOR_FOREACH(it, s)
|
|
{
|
|
Eina_Strbuf *buf = eina_strbuf_new();
|
|
const char *fs = ecore_file_file_get(s);
|
|
|
|
if (buf)
|
|
{
|
|
if ((res) && (fs))
|
|
{
|
|
eina_strbuf_append(buf, dst);
|
|
eina_strbuf_append(buf, "/");
|
|
eina_strbuf_append(buf, fs);
|
|
if (!fs_cp_rm(s, eina_strbuf_string_get(buf),
|
|
report_err, cp, rm))
|
|
res = EINA_FALSE;
|
|
}
|
|
eina_strbuf_free(buf);
|
|
}
|
|
eina_stringshare_del(s);
|
|
}
|
|
eina_iterator_free(it);
|
|
}
|
|
}
|
|
else if (S_ISLNK(st.st_mode))
|
|
{
|
|
if (cp)
|
|
{
|
|
char link[PATH_MAX];
|
|
ssize_t lnsz;
|
|
|
|
lnsz = readlink(src, link, sizeof(link));
|
|
if ((lnsz > 0) && (lnsz < (ssize_t)sizeof(link)))
|
|
{
|
|
if (symlink(link, dst) < 0)
|
|
{ // soft error? e.g. mv on FAT fs? report but move on
|
|
eina_strbuf_reset(sbuf);
|
|
eina_strbuf_append(sbuf, op);
|
|
eina_strbuf_append(sbuf, ": ");
|
|
eina_strbuf_append(sbuf, "Error creating symlink");
|
|
status_pos(1, eina_strbuf_string_get(sbuf));
|
|
}
|
|
}
|
|
else if (lnsz < 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT: // ignore this error - file removed during scan ?
|
|
eina_strbuf_reset(sbuf);
|
|
eina_strbuf_append(sbuf, op);
|
|
eina_strbuf_append(sbuf, ": ");
|
|
eina_strbuf_append(sbuf, "File vanished");
|
|
status_pos(1, eina_strbuf_string_get(sbuf));
|
|
break;
|
|
default:
|
|
_error_handle(src, dst, op, errno);
|
|
return EINA_FALSE;
|
|
}
|
|
}
|
|
else // 0 sized symlink ... WAT?
|
|
{
|
|
eina_strbuf_reset(sbuf);
|
|
eina_strbuf_append(sbuf, op);
|
|
eina_strbuf_append(sbuf, ": ");
|
|
eina_strbuf_append(sbuf, "Zero sized symlink");
|
|
status_error(src, NULL, eina_strbuf_string_get(sbuf));
|
|
}
|
|
}
|
|
}
|
|
else if (S_ISFIFO(st.st_mode))
|
|
{
|
|
if (cp)
|
|
{
|
|
if (mkfifo(dst, st.st_mode) < 0)
|
|
{ // soft error? ignore?
|
|
}
|
|
}
|
|
}
|
|
else if (S_ISSOCK(st.st_mode))
|
|
{
|
|
if (cp)
|
|
{
|
|
// we can't just make sockets - so ignore but document here
|
|
}
|
|
}
|
|
else if ((S_ISCHR(st.st_mode)) || (S_ISBLK(st.st_mode)))
|
|
{
|
|
if (cp)
|
|
{
|
|
if (mknod(dst, st.st_mode, st.st_dev) < 0)
|
|
{ // soft error? ignore?
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cp)
|
|
{
|
|
int fd_in, fd_ou;
|
|
void *old_copy_buf = NULL;
|
|
|
|
fd_in = open(src, O_RDONLY);
|
|
fd_ou = open(dst, O_WRONLY | O_CREAT, st.st_mode);
|
|
if ((fd_in >= 0) && (fd_ou >= 0))
|
|
{
|
|
ssize_t size = 1 * 1024 * 1024; // 1mb default
|
|
ssize_t ret, ret2;
|
|
off_t off_in = 0, off_ou = 0;
|
|
Eina_Bool old_copy = EINA_FALSE;
|
|
|
|
for (;;)
|
|
{
|
|
if (old_copy)
|
|
{
|
|
if (!old_copy_buf)
|
|
{
|
|
size = 256 * 1024; // drop to 256k buffer
|
|
old_copy_buf = malloc(size);
|
|
if (!old_copy_buf)
|
|
{
|
|
res = EINA_FALSE;
|
|
goto err_copy;
|
|
}
|
|
}
|
|
again_read:
|
|
ret = read(fd_in, old_copy_buf, size);
|
|
if (ret < 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EAGAIN:
|
|
case EINTR:
|
|
goto again_read;
|
|
default:
|
|
_error_handle(src, NULL, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err_copy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
off_in += ret;
|
|
again_write:
|
|
ret2 = write(fd_ou, old_copy_buf, ret);
|
|
if (ret2 < 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EAGAIN:
|
|
case EINTR:
|
|
goto again_write;
|
|
default:
|
|
_error_handle(NULL, dst, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err_copy;
|
|
}
|
|
}
|
|
else if (ret2 == ret)
|
|
{
|
|
off_ou += ret;
|
|
if (ret < size) break; // end of file
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = copy_file_range(fd_in, &off_in, fd_ou, &off_ou,
|
|
size, 0);
|
|
if (ret < 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EOPNOTSUPP:
|
|
case EXDEV:
|
|
old_copy = EINA_TRUE;
|
|
break;
|
|
case EBADF:
|
|
default: // WAT
|
|
_error_handle(src, dst, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err_copy;
|
|
}
|
|
}
|
|
else if (ret < size) break; // end of file
|
|
}
|
|
}
|
|
}
|
|
err_copy:
|
|
if (old_copy_buf) free(old_copy_buf);
|
|
if (fd_in >= 0) close(fd_in);
|
|
if (fd_ou >= 0) close(fd_ou);
|
|
}
|
|
}
|
|
if ((rm) && (res))
|
|
{
|
|
if (S_ISDIR(st.st_mode))
|
|
{
|
|
if (rmdir(src) != 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT: // ignore missing
|
|
break;
|
|
default:
|
|
_error_handle(src, NULL, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err;
|
|
}
|
|
}
|
|
if (unlink(src) != 0)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case ENOENT: // ignore missing
|
|
break;
|
|
default:
|
|
_error_handle(src, NULL, op, errno);
|
|
res = EINA_FALSE;
|
|
goto err_unlink;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err_unlink:
|
|
chown(dst, st.st_uid, st.st_gid); // ignore err
|
|
#ifdef STAT_NSEC
|
|
#ifdef st_mtime
|
|
#define STAT_NSEC_ATIME(st) (unsigned long long)((st)->st_atim.tv_nsec)
|
|
#define STAT_NSEC_MTIME(st) (unsigned long long)((st)->st_mtim.tv_nsec)
|
|
#define STAT_NSEC_CTIME(st) (unsigned long long)((st)->st_ctim.tv_nsec)
|
|
#else
|
|
#define STAT_NSEC_ATIME(st) (unsigned long long)((st)->st_atimensec)
|
|
#define STAT_NSEC_MTIME(st) (unsigned long long)((st)->st_mtimensec)
|
|
#define STAT_NSEC_CTIME(st) (unsigned long long)((st)->st_ctimensec)
|
|
#endif
|
|
#else
|
|
#define STAT_NSEC_ATIME(st) (unsigned long long)(0)
|
|
#define STAT_NSEC_MTIME(st) (unsigned long long)(0)
|
|
#define STAT_NSEC_CTIME(st) (unsigned long long)(0)
|
|
#endif
|
|
times[0].tv_sec = st.st_atime;
|
|
times[0].tv_usec = STAT_NSEC_ATIME(st) * 1000;
|
|
times[1].tv_sec = st.st_mtime;
|
|
times[1].tv_usec = STAT_NSEC_MTIME(st) * 1000;
|
|
utimes(dst, times); // ingore err
|
|
err:
|
|
umask(old_umask);
|
|
if (sbuf) eina_strbuf_free(sbuf);
|
|
return res;
|
|
}
|
|
|
|
Eina_Bool
|
|
fs_mv(const char *src, const char *dst, Eina_Bool report_err)
|
|
{ // mv /path/to/src/filename /path/to/dst/filename
|
|
Eina_Bool res = EINA_TRUE;
|
|
int ret;
|
|
const char *op = "Move";
|
|
|
|
status_op("mv");
|
|
status_count(1, src);
|
|
ret = rename(src, dst);
|
|
if (ret == 0) return res;
|
|
else
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EXDEV: // revert to cp + rm
|
|
return fs_cp_rm(src, dst, report_err, EINA_TRUE, EINA_TRUE);
|
|
break;
|
|
default:
|
|
if (report_err) _error_handle(src, dst, op, errno);
|
|
res = EINA_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
status_pos(1, src);
|
|
return res;
|
|
}
|