efm2/src/backends/default/fs.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;
}