766 lines
18 KiB
C
766 lines
18 KiB
C
/*
|
|
* $Id$
|
|
*
|
|
Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
|
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "fs-prv.h"
|
|
#include <qse/cmn/path.h>
|
|
#include <qse/cmn/str.h>
|
|
#include "../cmn/mem-prv.h"
|
|
|
|
#define NO_RECURSION 1
|
|
|
|
/* internal flags. it must not overlap with qse_fs_cpfile_flag_t enumerators */
|
|
#define CPFILE_DST_ATTR (1 << 29)
|
|
#define CPFILE_DST_FSPATH_MERGED (1 << 30)
|
|
|
|
struct devino_t
|
|
{
|
|
qse_uintmax_t dev;
|
|
qse_uintmax_t ino;
|
|
};
|
|
typedef struct devino_t devino_t;
|
|
|
|
struct cpfile_t
|
|
{
|
|
int flags;
|
|
qse_fs_char_t* src_fspath;
|
|
qse_fs_char_t* dst_fspath;
|
|
qse_fattr_t src_attr;
|
|
qse_fattr_t dst_attr;
|
|
|
|
};
|
|
typedef struct cpfile_t cpfile_t;
|
|
|
|
static int is_dir_remembered (qse_fs_t* fs, qse_fattr_t* attr)
|
|
{
|
|
qse_size_t i;
|
|
devino_t* di;
|
|
|
|
di = (devino_t*)fs->ddr.ptr;
|
|
for (i = 0; i < fs->ddr.len; i++)
|
|
{
|
|
if (attr->dev == di->dev && attr->ino == di->ino) return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int remember_dir_to_history (qse_fs_t* fs, qse_fattr_t* attr)
|
|
{
|
|
qse_size_t i;
|
|
devino_t* di;
|
|
|
|
di = (devino_t*)fs->ddr.ptr;
|
|
for (i = 0; i < fs->ddr.len; i++)
|
|
{
|
|
if (attr->dev == di->dev && attr->ino == di->ino)
|
|
{
|
|
fs->errnum = QSE_FS_EEXIST;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* TODO: table baded lookup instead of sequential search? */
|
|
if (fs->ddr.len >= fs->ddr.capa)
|
|
{
|
|
qse_size_t newcapa;
|
|
|
|
newcapa = QSE_ALIGNTO(fs->ddr.capa + 1, 256);
|
|
di = (devino_t*)QSE_MMGR_REALLOC (fs->mmgr, di, newcapa * QSE_SIZEOF(*di));
|
|
|
|
if (!di)
|
|
{
|
|
fs->errnum = QSE_FS_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
fs->ddr.ptr = di;
|
|
fs->ddr.capa = newcapa;
|
|
}
|
|
|
|
di[fs->ddr.len].dev = attr->dev;
|
|
di[fs->ddr.len].ino = attr->ino;
|
|
fs->ddr.len++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int merge_dstdir_and_srcbase (qse_fs_t* fs, cpfile_t* cpfile)
|
|
{
|
|
qse_fs_char_t* fstmp;
|
|
|
|
/* if the destination is directory, copy the base name of the source
|
|
* and append it to the end of the destination, targetting at an entry
|
|
* in the directory */
|
|
QSE_ASSERT (cpfile->dst_attr.isdir);
|
|
|
|
fstmp = merge_fspath_dup (cpfile->dst_fspath, get_fspath_base (cpfile->src_fspath), fs->mmgr);
|
|
if (!fstmp)
|
|
{
|
|
fs->errnum = QSE_FS_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
qse_fs_freefspath (fs, QSE_NULL, cpfile->dst_fspath);
|
|
cpfile->dst_fspath = fstmp;
|
|
|
|
if (qse_fs_getattrsys (fs, cpfile->dst_fspath, &cpfile->dst_attr, QSE_FILE_ATTR_SYMLINK) <= -1)
|
|
{
|
|
/* attribute on the new destination is not available */
|
|
cpfile->flags &= ~CPFILE_DST_ATTR;
|
|
}
|
|
else
|
|
{
|
|
/* the attribute has been updated to reflect the new destination */
|
|
cpfile->flags |= CPFILE_DST_ATTR;
|
|
}
|
|
|
|
|
|
cpfile->flags |= CPFILE_DST_FSPATH_MERGED;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
#if defined(_WIN32)
|
|
DWORD copy_file_progress (
|
|
LARGE_INTEGER TotalFileSize,
|
|
LARGE_INTEGER TotalBytesTransferred,
|
|
LARGE_INTEGER StreamSize,
|
|
LARGE_INTEGER StreamBytesTransferred,
|
|
DWORD dwStreamNumber,
|
|
DWORD dwCallbackReason,
|
|
HANDLE hSourceFile,
|
|
HANDLE hDestinationFile,
|
|
LPVOID lpData)
|
|
{
|
|
}
|
|
#endif
|
|
*/
|
|
|
|
/* copy
|
|
* -> progress
|
|
* -> abort/cancel
|
|
* -> replace/overwrite
|
|
* -> symbolic link
|
|
*/
|
|
|
|
#if defined(_WIN32) && !defined(COPY_FILE_COPY_SYMLINK)
|
|
/* winbase.h defines this only if _WIN32_WINNT >= 0x0600 */
|
|
# define COPY_FILE_COPY_SYMLINK 0x00000800L
|
|
#endif
|
|
|
|
static int copy_file_in_fs (qse_fs_t* fs, cpfile_t* cpfile)
|
|
{
|
|
#if defined(_WIN32)
|
|
/* ------------------------------------------------------ */
|
|
DWORD copy_flags = 0;
|
|
|
|
if (cpfile->flags & QSE_FS_CPFILE_SYMLINK)
|
|
copy_flags |= COPY_FILE_COPY_SYMLINK;
|
|
if (!(cpfile->flags & QSE_FS_CPFILE_REPLACE))
|
|
copy_flags |= COPY_FILE_FAIL_IF_EXISTS;
|
|
|
|
/*
|
|
if (fs->cbs.cp)
|
|
{
|
|
Specify callback???
|
|
}
|
|
*/
|
|
|
|
if (CopyFileEx (cpfile->src_fspath, cpfile->dst_fspath, QSE_NULL, QSE_NULL, QSE_NULL, copy_flags) == FALSE)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, GetLastError());
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
/* ------------------------------------------------------ */
|
|
|
|
#elif defined(__OS2__)
|
|
/* ------------------------------------------------------ */
|
|
|
|
APIRET rc;
|
|
ULONG opmode = 0;
|
|
|
|
if (cpfile->flags & QSE_FS_CPFILE_REPLACE) opmode |= 1; /* set bit 0 */
|
|
|
|
rc = DosCopy (cpfile->src_fspath, cpfile->dst_fspath, opmode);
|
|
if (rc != NO_ERROR)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, rc);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
/* ------------------------------------------------------ */
|
|
|
|
#elif defined(__DOS__)
|
|
|
|
/* ------------------------------------------------------ */
|
|
|
|
/* TOOD: IMPLEMENT THIS */
|
|
fs->errnum = QSE_FS_ENOIMPL;
|
|
return -1;
|
|
|
|
/* ------------------------------------------------------ */
|
|
|
|
#else
|
|
|
|
if ((cpfile->flags & QSE_FS_CPFILE_SYMLINK) && cpfile->src_attr.islnk)
|
|
{
|
|
/* the source file is a symbolic link and the SYMLINK option is on.*/
|
|
|
|
qse_fs_char_t* tmpbuf;
|
|
|
|
/* TODO: use a static buffer is size is small enough */
|
|
tmpbuf = QSE_MMGR_ALLOC (fs->mmgr, QSE_SIZEOF(*tmpbuf) * (cpfile->src_attr.size + 1));
|
|
if (tmpbuf == QSE_NULL)
|
|
{
|
|
fs->errnum = QSE_FS_ENOMEM;
|
|
return -1;
|
|
}
|
|
QSE_MEMSET (tmpbuf, 0, QSE_SIZEOF(*tmpbuf) * (cpfile->src_attr.size + 1));
|
|
|
|
if (QSE_READLINK (cpfile->src_fspath, tmpbuf, cpfile->src_attr.size) <= -1)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
|
|
QSE_MMGR_FREE (fs->mmgr, tmpbuf);
|
|
return -1;
|
|
}
|
|
|
|
if (QSE_SYMLINK (tmpbuf, cpfile->dst_fspath) <= -1)
|
|
{
|
|
if (cpfile->flags & QSE_FS_CPFILE_FORCE)
|
|
{
|
|
QSE_UNLINK (cpfile->dst_fspath);
|
|
if (fs->cbs.actcb && qse_fs_invokeactcb (fs, QSE_FS_RMFILE, cpfile->dst_fspath, QSE_NULL, 0, 0) <= -1) goto oops_1;
|
|
|
|
if (QSE_SYMLINK (tmpbuf, cpfile->dst_fspath) <= -1)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
|
|
goto oops_1;
|
|
}
|
|
}
|
|
}
|
|
if (fs->cbs.actcb && qse_fs_invokeactcb (fs, QSE_FS_SYMLINK, tmpbuf, cpfile->dst_fspath, 0, 0) <= -1) goto oops_1;
|
|
|
|
QSE_MMGR_FREE (fs->mmgr, tmpbuf);
|
|
return 0;
|
|
|
|
oops_1:
|
|
if (tmpbuf) QSE_MMGR_FREE (fs->mmgr, tmpbuf);
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
int in = -1, out = -1;
|
|
qse_ssize_t in_len, out_len;
|
|
qse_uint8_t* bp;
|
|
qse_uintmax_t bytes_copied = 0;
|
|
qse_char_t* srcpath = QSE_NULL, * dstpath = QSE_NULL;
|
|
|
|
in = QSE_OPEN (cpfile->src_fspath, O_RDONLY, 0);
|
|
if (in <= -1)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
|
|
goto oops_2;
|
|
}
|
|
|
|
out = QSE_OPEN (cpfile->dst_fspath, O_CREAT | O_WRONLY | O_TRUNC, cpfile->src_attr.mode);
|
|
if (out <= -1 && (cpfile->flags & QSE_FS_CPFILE_FORCE))
|
|
{
|
|
/* if forced, delete it and try to open it again */
|
|
/* TODO: call cbs.rm callback??? */
|
|
QSE_UNLINK (cpfile->dst_fspath);
|
|
out = QSE_OPEN (cpfile->dst_fspath, O_CREAT | O_WRONLY | O_TRUNC, cpfile->src_attr.mode);
|
|
}
|
|
|
|
if (out <= -1)
|
|
{
|
|
fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
|
|
goto oops_2;
|
|
}
|
|
|
|
if (fs->cbs.actcb)
|
|
{
|
|
srcpath = (qse_char_t*)make_str_with_fspath (fs, cpfile->src_fspath);
|
|
dstpath = (qse_char_t*)make_str_with_fspath (fs, cpfile->dst_fspath);
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
/* TODO: use vmsplice() and family for faster copying... */
|
|
in_len = QSE_READ (in, fs->cpbuf, QSE_SIZEOF(fs->cpbuf));
|
|
if (in_len <= 0)
|
|
{
|
|
if (bytes_copied == 0 && srcpath && dstpath)
|
|
{
|
|
if (fs->cbs.actcb (fs, QSE_FS_CPFILE, srcpath, dstpath, cpfile->src_attr.size, bytes_copied) <= -1) goto oops_2;
|
|
}
|
|
break;
|
|
}
|
|
|
|
bp = fs->cpbuf;
|
|
while (in_len > 0)
|
|
{
|
|
out_len = QSE_WRITE (out, bp, in_len);
|
|
if (out_len <= -1) goto oops_2;
|
|
bp += out_len;
|
|
in_len -= out_len;
|
|
bytes_copied += out_len;
|
|
}
|
|
|
|
if (srcpath && dstpath)
|
|
{
|
|
if (fs->cbs.actcb (fs, QSE_FS_CPFILE, srcpath, dstpath, cpfile->src_attr.size, bytes_copied) <= -1) goto oops_2;
|
|
}
|
|
}
|
|
|
|
if (cpfile->flags & QSE_FS_CPFILE_PRESERVE)
|
|
{
|
|
if (qse_fs_setattronfd (fs, out, &cpfile->src_attr, QSE_FILE_ATTR_TIME | QSE_FILE_ATTR_OWNER | QSE_FILE_ATTR_MODE) <= -1)
|
|
{
|
|
if (fs->errnum == QSE_FS_ENOIMPL)
|
|
{
|
|
if (qse_fs_setattrsys (fs, cpfile->dst_fspath, &cpfile->src_attr, QSE_FILE_ATTR_TIME | QSE_FILE_ATTR_OWNER | QSE_FILE_ATTR_MODE) <= -1) goto oops_2;
|
|
}
|
|
}
|
|
}
|
|
|
|
QSE_CLOSE (out);
|
|
QSE_CLOSE (in);
|
|
|
|
if (srcpath) free_str_with_fspath (fs, cpfile->src_fspath, srcpath);
|
|
if (dstpath) free_str_with_fspath (fs, cpfile->dst_fspath, dstpath);
|
|
return 0;
|
|
|
|
oops_2:
|
|
if (out >= 0) QSE_CLOSE (out);
|
|
if (in >= 0) QSE_CLOSE (in);
|
|
|
|
if (srcpath) free_str_with_fspath (fs, cpfile->src_fspath, srcpath);
|
|
if (dstpath) free_str_with_fspath (fs, cpfile->dst_fspath, dstpath);
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int prepare_cpfile (qse_fs_t* fs, cpfile_t* cpfile)
|
|
{
|
|
/* error if the source file can't be stat'ed.
|
|
* ok if the destination file can't be stat'ed */
|
|
/* TODO: check if flags to getattrsys() is correct */
|
|
if (qse_fs_getattrsys (fs, cpfile->src_fspath, &cpfile->src_attr, QSE_FILE_ATTR_SYMLINK) <= -1) return -1;
|
|
if (qse_fs_getattrsys (fs, cpfile->dst_fspath, &cpfile->dst_attr, QSE_FILE_ATTR_SYMLINK) >= 0) cpfile->flags |= CPFILE_DST_ATTR;
|
|
return 0;
|
|
}
|
|
|
|
static void clear_cpfile (qse_fs_t* fs, cpfile_t* cpfile)
|
|
{
|
|
if (cpfile->src_fspath)
|
|
{
|
|
QSE_MMGR_FREE (fs->mmgr, cpfile->src_fspath);
|
|
cpfile->src_fspath = QSE_NULL;
|
|
}
|
|
if (cpfile->dst_fspath)
|
|
{
|
|
QSE_MMGR_FREE (fs->mmgr, cpfile->dst_fspath);
|
|
cpfile->dst_fspath = QSE_NULL;
|
|
}
|
|
}
|
|
|
|
/* copy file stack - stack for file copying */
|
|
typedef struct cfs_t cfs_t;
|
|
struct cfs_t
|
|
{
|
|
cpfile_t cpfile;
|
|
qse_dir_t* dir;
|
|
cfs_t* next;
|
|
};
|
|
|
|
static cfs_t* push_cfs (qse_fs_t* fs, const cpfile_t* cpfile, qse_dir_t* dir)
|
|
{
|
|
cfs_t* cfs;
|
|
|
|
cfs = (cfs_t*)QSE_MMGR_ALLOC (fs->mmgr, QSE_SIZEOF(*cfs));
|
|
if (cfs == QSE_NULL)
|
|
{
|
|
fs->errnum = QSE_FS_ENOMEM;
|
|
return QSE_NULL;
|
|
}
|
|
|
|
cfs->cpfile = *cpfile;
|
|
cfs->dir = dir;
|
|
cfs->next = fs->cfs;
|
|
|
|
fs->cfs = cfs;
|
|
return cfs;
|
|
}
|
|
|
|
static void pop_cfs (qse_fs_t* fs, cpfile_t* cpfile, qse_dir_t** dir)
|
|
{
|
|
cfs_t* cfs;
|
|
|
|
cfs = fs->cfs;
|
|
QSE_ASSERT (cfs != QSE_NULL);
|
|
fs->cfs = cfs->next;
|
|
|
|
if (cpfile) *cpfile = cfs->cpfile;
|
|
else clear_cpfile (fs, &cfs->cpfile);
|
|
|
|
if (dir) *dir = cfs->dir;
|
|
else qse_dir_close (cfs->dir);
|
|
|
|
QSE_MMGR_FREE (fs->mmgr, cfs);
|
|
}
|
|
|
|
static int copy_file (qse_fs_t* fs, cpfile_t* cpfile)
|
|
{
|
|
#if defined(NO_RECURSION)
|
|
cfs_t* cfs;
|
|
#else
|
|
cpfile_t sub_cpfile;
|
|
#endif
|
|
qse_dir_t* dir = QSE_NULL;
|
|
qse_dir_errnum_t direrr;
|
|
qse_dir_ent_t dirent;
|
|
int x;
|
|
|
|
start_over:
|
|
if (cpfile->src_attr.isdir)
|
|
{
|
|
/* source is a directory */
|
|
if (cpfile->flags & CPFILE_DST_ATTR)
|
|
{
|
|
if (!cpfile->dst_attr.isdir)
|
|
{
|
|
/* cannot copy a directory to a file */
|
|
fs->errnum = QSE_FS_ENOTDIR;
|
|
goto oops;
|
|
}
|
|
|
|
/* the destination is also a directory */
|
|
|
|
if (cpfile->src_attr.ino == cpfile->dst_attr.ino &&
|
|
cpfile->src_attr.dev == cpfile->dst_attr.dev)
|
|
{
|
|
/* cannot copy a directory to itself */
|
|
fs->errnum = QSE_FS_EPERM;
|
|
goto oops;
|
|
}
|
|
|
|
if (merge_dstdir_and_srcbase (fs, cpfile) <= -1) goto oops;
|
|
|
|
/*
|
|
if (!(cpfile->flags & QSE_FS_CPFILE_REPLACE))
|
|
{
|
|
fs->errnum = QSE_FS_EEXIST;
|
|
return -1;
|
|
}*/
|
|
}
|
|
|
|
if (!(cpfile->flags & QSE_FS_CPFILE_RECURSIVE))
|
|
{
|
|
/* cannot copy a directory without recursion */
|
|
fs->errnum = QSE_FS_EISDIR;
|
|
goto oops;
|
|
}
|
|
|
|
|
|
if (is_dir_remembered (fs, &cpfile->src_attr))
|
|
{
|
|
/* skip this source directory as it's one of the destination
|
|
* directores earlier. see the call to remember_dir_to_history()
|
|
* below. */
|
|
goto skip_dir;
|
|
}
|
|
|
|
/* both the source and the destion is a directory */
|
|
dir = qse_dir_open (
|
|
fs->mmgr, 0, (const qse_char_t*)cpfile->src_fspath,
|
|
#if defined(QSE_FS_CHAR_IS_MCHAR)
|
|
QSE_DIR_SKIPSPCDIR | QSE_DIR_MBSPATH,
|
|
#else
|
|
QSE_DIR_SKIPSPCDIR | QSE_DIR_WCSPATH,
|
|
#endif
|
|
&direrr
|
|
);
|
|
if (!dir)
|
|
{
|
|
fs->errnum = qse_fs_direrrtoerrnum (fs, direrr);
|
|
goto oops;
|
|
}
|
|
|
|
if (qse_fs_mkdirsys(fs, cpfile->dst_fspath, 0777) <= -1) /* TODO: copy mode? */
|
|
{
|
|
/* it's ok if the destination directory already exists */
|
|
if (fs->errnum != QSE_FS_EEXIST) goto oops;
|
|
}
|
|
|
|
/* (re)load the attribute. */
|
|
if (qse_fs_getattrsys (fs, cpfile->dst_fspath, &cpfile->dst_attr, QSE_FILE_ATTR_SYMLINK) <= -1) goto oops;
|
|
cpfile->flags |= CPFILE_DST_ATTR;
|
|
|
|
if (remember_dir_to_history (fs, &cpfile->dst_attr) <= -1)
|
|
{
|
|
/* if the directory has been copied once, fs->errnum should be QSE_FS_EEXIST */
|
|
if (fs->errnum == QSE_FS_EEXIST) goto close_dir;
|
|
goto oops;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
x = qse_dir_read (dir, &dirent);
|
|
if (x <= -1)
|
|
{
|
|
fs->errnum = qse_fs_direrrtoerrnum (fs, qse_dir_geterrnum(dir));
|
|
goto oops;
|
|
}
|
|
if (x == 0) break; /* no more entries */
|
|
|
|
#if defined(NO_RECURSION)
|
|
cfs = push_cfs (fs, cpfile, dir);
|
|
if (!cfs) goto oops;
|
|
|
|
QSE_MEMSET (cpfile, 0, QSE_SIZEOF(*cpfile));
|
|
dir = QSE_NULL;
|
|
|
|
cpfile->flags = cfs->cpfile.flags & QSE_FS_CPFILE_ALL; /* inherit public flags */
|
|
cpfile->src_fspath = merge_fspath_dup (cfs->cpfile.src_fspath, (qse_fs_char_t*)dirent.name, fs->mmgr);
|
|
cpfile->dst_fspath = merge_fspath_dup (cfs->cpfile.dst_fspath, (qse_fs_char_t*)dirent.name, fs->mmgr);
|
|
if (!cpfile->src_fspath || !cpfile->dst_fspath || prepare_cpfile (fs, cpfile) <= -1)
|
|
{
|
|
clear_cpfile (fs, cpfile);
|
|
goto oops;
|
|
}
|
|
|
|
goto start_over;
|
|
|
|
resume_copy_dir:
|
|
/* do nothing. loop over */
|
|
;
|
|
|
|
#else
|
|
QSE_MEMSET (&sub_cpfile, 0, QSE_SIZEOF(sub_cpfile));
|
|
sub_cpfile.flags = cpfile->flags & QSE_FS_CPFILE_ALL; /* inherit public flags */
|
|
sub_cpfile.src_fspath = merge_fspath_dup (cpfile->src_fspath, (qse_fs_char_t*)dirent.name, fs->mmgr);
|
|
sub_cpfile.dst_fspath = merge_fspath_dup (cpfile->dst_fspath, (qse_fs_char_t*)dirent.name, fs->mmgr);
|
|
if (!sub_cpfile.src_fspath || !sub_cpfile.dst_fspath || prepare_cpfile (fs, &sub_cpfile) <= -1)
|
|
{
|
|
clear_cpfile (fs, &sub_cpfile);
|
|
goto oops;
|
|
}
|
|
x = copy_file (fs, &sub_cpfile); /* TODO: remove recursion */
|
|
|
|
clear_cpfile (fs, &sub_cpfile);
|
|
if (x <= -1) goto oops;
|
|
#endif
|
|
}
|
|
|
|
close_dir:
|
|
qse_dir_close (dir);
|
|
dir = QSE_NULL;
|
|
|
|
skip_dir:
|
|
/* do nothing more */ ;
|
|
}
|
|
else
|
|
{
|
|
/* source is a file */
|
|
|
|
copy_file:
|
|
if (cpfile->flags & CPFILE_DST_ATTR)
|
|
{
|
|
/* attribute of the destination path is available */
|
|
|
|
if (cpfile->src_attr.ino == cpfile->dst_attr.ino &&
|
|
cpfile->src_attr.dev == cpfile->dst_attr.dev)
|
|
{
|
|
/* cannot copy a file to itself */
|
|
fs->errnum = QSE_FS_EPERM;
|
|
goto oops;
|
|
}
|
|
|
|
if (cpfile->dst_attr.isdir)
|
|
{
|
|
/* arrange to copy a file into a directory */
|
|
if (merge_dstdir_and_srcbase (fs, cpfile) <= -1) return -1;
|
|
goto copy_file;
|
|
}
|
|
|
|
if (!(cpfile->flags & QSE_FS_CPFILE_REPLACE))
|
|
{
|
|
fs->errnum = QSE_FS_EEXIST;
|
|
goto oops;
|
|
}
|
|
}
|
|
|
|
/* both source and target are files */
|
|
if (copy_file_in_fs (fs, cpfile) <= -1) goto oops;
|
|
}
|
|
|
|
skip:
|
|
#if defined(NO_RECURSION)
|
|
if (fs->cfs)
|
|
{
|
|
clear_cpfile (fs, cpfile); /* clear key data created for subitems in the directory */
|
|
pop_cfs (fs, cpfile, &dir); /* restore key data for the original directory*/
|
|
goto resume_copy_dir;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
oops:
|
|
#if defined(NO_RECURSION)
|
|
while (fs->cfs) pop_cfs (fs, QSE_NULL, QSE_NULL);
|
|
#endif
|
|
if (dir) qse_dir_close (dir);
|
|
return -1;
|
|
}
|
|
|
|
/* ========================================================================= */
|
|
|
|
struct glob_ctx_t
|
|
{
|
|
qse_fs_t* fs;
|
|
int flags;
|
|
void* dstpath;
|
|
int wcs;
|
|
};
|
|
typedef struct glob_ctx_t glob_ctx_t;
|
|
|
|
static int copy_file_for_glob (const void* path, void* ctx)
|
|
{
|
|
glob_ctx_t* gc = (glob_ctx_t*)ctx;
|
|
cpfile_t cpfile;
|
|
int ret;
|
|
|
|
QSE_MEMSET (&cpfile, 0, QSE_SIZEOF(cpfile));
|
|
|
|
cpfile.flags = gc->flags & QSE_FS_CPFILE_ALL; /* keep public flags only */
|
|
|
|
if (gc->wcs)
|
|
{
|
|
cpfile.src_fspath = (qse_fs_char_t*)qse_fs_dupfspathforwcs (gc->fs, ((qse_wcstr_t*)path)->ptr);
|
|
cpfile.dst_fspath = (qse_fs_char_t*)qse_fs_dupfspathforwcs (gc->fs, gc->dstpath);
|
|
}
|
|
else
|
|
{
|
|
cpfile.src_fspath = (qse_fs_char_t*)qse_fs_dupfspathformbs (gc->fs, ((qse_mcstr_t*)path)->ptr);
|
|
cpfile.dst_fspath = (qse_fs_char_t*)qse_fs_dupfspathformbs (gc->fs, gc->dstpath);
|
|
}
|
|
if (!cpfile.src_fspath || !cpfile.dst_fspath || prepare_cpfile (gc->fs, &cpfile) <= -1) goto oops;
|
|
|
|
ret = copy_file (gc->fs, &cpfile);
|
|
|
|
clear_cpfile (gc->fs, &cpfile);
|
|
|
|
return ret;
|
|
|
|
oops:
|
|
clear_cpfile (gc->fs, &cpfile);
|
|
return -1;
|
|
}
|
|
|
|
int qse_fs_cpfilembs (qse_fs_t* fs, const qse_mchar_t* srcpath, const qse_mchar_t* dstpath, int flags)
|
|
{
|
|
glob_ctx_t ctx;
|
|
int ret = 0;
|
|
|
|
ctx.fs = fs;
|
|
ctx.flags = flags;
|
|
ctx.dstpath = (void*)dstpath;
|
|
ctx.wcs = 0;
|
|
|
|
if (flags & QSE_FS_CPFILE_GLOB)
|
|
{
|
|
fs->errnum = QSE_FS_ENOERR;
|
|
if (qse_globmbs(srcpath, copy_file_for_glob, &ctx, DEFAULT_GLOB_FLAGS, fs->mmgr, fs->cmgr) <= -1)
|
|
{
|
|
if (fs->errnum == QSE_FS_ENOERR) fs->errnum = QSE_FS_EGLOB;
|
|
ret = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qse_mcstr_t s;
|
|
s.ptr = (qse_mchar_t*)srcpath;
|
|
s.len = 0; /* not important */
|
|
/* let me use the glob callback function for simplicity */
|
|
ret = copy_file_for_glob (&s, &ctx);
|
|
}
|
|
|
|
if (fs->ddr.ptr)
|
|
{
|
|
QSE_MMGR_FREE (fs->mmgr, fs->ddr.ptr);
|
|
fs->ddr.ptr = QSE_NULL;
|
|
fs->ddr.len = 0;
|
|
fs->ddr.capa = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int qse_fs_cpfilewcs (qse_fs_t* fs, const qse_wchar_t* srcpath, const qse_wchar_t* dstpath, int flags)
|
|
{
|
|
glob_ctx_t ctx;
|
|
int ret = 0;
|
|
|
|
ctx.fs = fs;
|
|
ctx.flags = flags;
|
|
ctx.dstpath = (void*)dstpath;
|
|
ctx.wcs = 1;
|
|
|
|
if (flags & QSE_FS_CPFILE_GLOB)
|
|
{
|
|
fs->errnum = QSE_FS_ENOERR;
|
|
if (qse_globwcs(srcpath, copy_file_for_glob, &ctx, DEFAULT_GLOB_FLAGS, fs->mmgr, fs->cmgr) <= -1)
|
|
{
|
|
if (fs->errnum == QSE_FS_ENOERR) fs->errnum = QSE_FS_EGLOB;
|
|
ret = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* let me use the glob callback function for simplicity */
|
|
qse_wcstr_t s;
|
|
s.ptr = (qse_wchar_t*)srcpath;
|
|
s.len = 0; /* not important */
|
|
ret = copy_file_for_glob (&s, &ctx);
|
|
}
|
|
|
|
if (fs->ddr.ptr)
|
|
{
|
|
QSE_MMGR_FREE (fs->mmgr, fs->ddr.ptr);
|
|
fs->ddr.ptr = QSE_NULL;
|
|
fs->ddr.len = 0;
|
|
fs->ddr.capa = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|