mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
575 lines
16 KiB
C++
575 lines
16 KiB
C++
/*
|
|
Copyright (C) 2000-2002 Stephan Kulow <coolo@kde.org>
|
|
Copyright (C) 2000-2002 David Faure <faure@kde.org>
|
|
Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
|
|
Copyright (C) 2006 Allan Sandfeld Jensen <sandfeld@kde.org>
|
|
Copyright (C) 2007 Thiago Macieira <thiago@kde.org>
|
|
Copyright (C) 2007 Christian Ehrlicher <ch.ehrlicher@gmx.de>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License (LGPL) as published by the Free Software Foundation;
|
|
either version 2 of the License, or (at your option) any later
|
|
version.
|
|
|
|
This library 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#define QT_NO_CAST_FROM_ASCII
|
|
|
|
#include "file.h"
|
|
|
|
#include <config.h>
|
|
#include <config-kioslave-file.h>
|
|
#include <config-acl.h>
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QDir>
|
|
|
|
#include <kde_file.h>
|
|
#include <kdebug.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kmountpoint.h>
|
|
#include <kuser.h>
|
|
|
|
#include <dirent.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <utime.h>
|
|
#include <stdlib.h>
|
|
|
|
#ifdef HAVE_POSIX_ACL
|
|
# include <sys/acl.h>
|
|
# include <acl/libacl.h>
|
|
#endif
|
|
|
|
// sendfile has different semantics in different platforms
|
|
#if defined HAVE_SENDFILE && defined Q_OS_LINUX
|
|
# define USE_SENDFILE 1
|
|
#endif
|
|
|
|
#ifdef USE_SENDFILE
|
|
# include <sys/sendfile.h>
|
|
#endif
|
|
|
|
using namespace KIO;
|
|
|
|
#define MAX_IPC_SIZE (1024*32)
|
|
|
|
static bool same_inode(const KDE_struct_stat &src, const KDE_struct_stat &dest)
|
|
{
|
|
if (src.st_ino == dest.st_ino && src.st_dev == dest.st_dev) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern int write_all(int fd, const char *buf, size_t len);
|
|
|
|
void FileProtocol::copy(const KUrl &srcUrl, const KUrl &destUrl,
|
|
int _mode, JobFlags _flags)
|
|
{
|
|
kDebug(7101) << "copy(): " << srcUrl << " -> " << destUrl << ", mode=" << _mode;
|
|
|
|
const QString src = srcUrl.toLocalFile();
|
|
const QString dest = destUrl.toLocalFile();
|
|
QByteArray _src( QFile::encodeName(src));
|
|
QByteArray _dest( QFile::encodeName(dest));
|
|
KDE_struct_stat buff_src;
|
|
#ifdef HAVE_POSIX_ACL
|
|
acl_t acl;
|
|
#endif
|
|
|
|
if (KDE_stat(_src.data(), &buff_src) == -1) {
|
|
if (errno == EACCES) {
|
|
error(KIO::ERR_ACCESS_DENIED, src);
|
|
} else {
|
|
error(KIO::ERR_DOES_NOT_EXIST, src);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (S_ISDIR(buff_src.st_mode)) {
|
|
error(KIO::ERR_IS_DIRECTORY, src);
|
|
return;
|
|
}
|
|
if (S_ISFIFO(buff_src.st_mode) || S_ISSOCK(buff_src.st_mode)) {
|
|
error(KIO::ERR_CANNOT_OPEN_FOR_READING, src);
|
|
return;
|
|
}
|
|
|
|
KDE_struct_stat buff_dest;
|
|
bool dest_exists = (KDE_lstat(_dest.data(), &buff_dest) != -1);
|
|
if (dest_exists) {
|
|
if (same_inode(buff_dest, buff_src)) {
|
|
error(KIO::ERR_IDENTICAL_FILES, dest);
|
|
return;
|
|
}
|
|
|
|
if (S_ISDIR(buff_dest.st_mode)) {
|
|
error(KIO::ERR_DIR_ALREADY_EXIST, dest);
|
|
return;
|
|
}
|
|
|
|
if (!(_flags & KIO::Overwrite)) {
|
|
error(KIO::ERR_FILE_ALREADY_EXIST, dest);
|
|
return;
|
|
}
|
|
|
|
// If the destination is a symlink and overwrite is TRUE,
|
|
// remove the symlink first to prevent the scenario where
|
|
// the symlink actually points to current source!
|
|
if ((_flags & KIO::Overwrite) && S_ISLNK(buff_dest.st_mode)) {
|
|
//kDebug(7101) << "copy(): LINK DESTINATION";
|
|
remove(_dest.data());
|
|
}
|
|
}
|
|
|
|
int src_fd = KDE_open(_src.data(), O_RDONLY);
|
|
if (src_fd < 0) {
|
|
error(KIO::ERR_CANNOT_OPEN_FOR_READING, src);
|
|
return;
|
|
}
|
|
|
|
#if HAVE_FADVISE
|
|
posix_fadvise(src_fd,0,0,POSIX_FADV_SEQUENTIAL);
|
|
#endif
|
|
|
|
// WABA: Make sure that we keep writing permissions ourselves,
|
|
// otherwise we can be in for a surprise on NFS.
|
|
mode_t initialMode;
|
|
if (_mode != -1) {
|
|
initialMode = _mode | S_IWUSR;
|
|
} else {
|
|
initialMode = 0666;
|
|
}
|
|
|
|
int dest_fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode);
|
|
if (dest_fd < 0) {
|
|
kDebug(7101) << "###### COULD NOT WRITE " << dest;
|
|
if (errno == EACCES) {
|
|
error(KIO::ERR_WRITE_ACCESS_DENIED, dest);
|
|
} else {
|
|
error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest);
|
|
}
|
|
::close(src_fd);
|
|
return;
|
|
}
|
|
|
|
#if HAVE_FADVISE
|
|
posix_fadvise(dest_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
|
|
#endif
|
|
|
|
#ifdef HAVE_POSIX_ACL
|
|
acl = acl_get_fd(src_fd);
|
|
if (acl && acl_equiv_mode(acl, 0) == 0) {
|
|
kDebug(7101) << _dest.data() << " doesn't have extended ACL";
|
|
acl_free(acl);
|
|
acl = NULL;
|
|
}
|
|
#endif
|
|
totalSize(buff_src.st_size);
|
|
|
|
KIO::filesize_t processed_size = 0;
|
|
char buffer[MAX_IPC_SIZE];
|
|
int n = 0;
|
|
#ifdef USE_SENDFILE
|
|
bool use_sendfile = buff_src.st_size < 0x7FFFFFFF;
|
|
#endif
|
|
while (true) {
|
|
#ifdef USE_SENDFILE
|
|
if (use_sendfile) {
|
|
off_t sf = processed_size;
|
|
n = ::sendfile(dest_fd, src_fd, &sf, MAX_IPC_SIZE);
|
|
processed_size = sf;
|
|
if (n == -1 && (errno == EINVAL || errno == ENOSYS)) {
|
|
// not all filesystems support sendfile()
|
|
kDebug(7101) << "sendfile() not supported, falling back ";
|
|
use_sendfile = false;
|
|
}
|
|
}
|
|
if (!use_sendfile)
|
|
#endif
|
|
n = ::read(src_fd, buffer, MAX_IPC_SIZE);
|
|
|
|
if (n == -1) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
#ifdef USE_SENDFILE
|
|
if (use_sendfile) {
|
|
kDebug(7101) << "sendfile() error:" << strerror(errno);
|
|
if (errno == ENOSPC) {
|
|
// disk full
|
|
error(KIO::ERR_DISK_FULL, dest);
|
|
remove(_dest.data());
|
|
} else {
|
|
error(KIO::ERR_SLAVE_DEFINED,
|
|
i18n("Cannot copy file from %1 to %2. (Errno: %3)",
|
|
src, dest, errno));
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
error(KIO::ERR_COULD_NOT_READ, src);
|
|
::close(src_fd);
|
|
::close(dest_fd);
|
|
}
|
|
#ifdef HAVE_POSIX_ACL
|
|
if (acl) {
|
|
acl_free(acl);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
if (n == 0) {
|
|
break; // Finished
|
|
}
|
|
#ifdef USE_SENDFILE
|
|
if (!use_sendfile) {
|
|
#endif
|
|
if (write_all(dest_fd, buffer, n)) {
|
|
::close(src_fd);
|
|
::close(dest_fd);
|
|
|
|
if (errno == ENOSPC) {
|
|
// disk full
|
|
error(KIO::ERR_DISK_FULL, dest);
|
|
remove(_dest.data());
|
|
} else {
|
|
kWarning(7101) << "Couldn't write[2]. Error:" << strerror(errno);
|
|
error(KIO::ERR_COULD_NOT_WRITE, dest);
|
|
}
|
|
#ifdef HAVE_POSIX_ACL
|
|
if (acl) {
|
|
acl_free(acl);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
processed_size += n;
|
|
#ifdef USE_SENDFILE
|
|
}
|
|
#endif
|
|
processedSize(processed_size);
|
|
}
|
|
|
|
::close(src_fd);
|
|
|
|
if (::close(dest_fd)) {
|
|
kWarning(7101) << "Error when closing file descriptor[2]:" << strerror(errno);
|
|
error(KIO::ERR_COULD_NOT_WRITE, dest);
|
|
#ifdef HAVE_POSIX_ACL
|
|
if (acl) {
|
|
acl_free(acl);
|
|
};
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// set final permissions
|
|
if (_mode != -1) {
|
|
if ((::chmod(_dest.data(), _mode) != 0)
|
|
#ifdef HAVE_POSIX_ACL
|
|
|| (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0)
|
|
#endif
|
|
)
|
|
{
|
|
KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(dest);
|
|
// Eat the error if the filesystem apparently doesn't support chmod.
|
|
if (mp && mp->testFileSystemFlag(KMountPoint::SupportsChmod)) {
|
|
warning(i18n("Could not change permissions for\n%1", dest));
|
|
}
|
|
}
|
|
}
|
|
#ifdef HAVE_POSIX_ACL
|
|
if (acl) {
|
|
acl_free(acl);
|
|
}
|
|
#endif
|
|
|
|
// copy access and modification time
|
|
struct utimbuf ut;
|
|
ut.actime = buff_src.st_atime;
|
|
ut.modtime = buff_src.st_mtime;
|
|
if (::utime(_dest.data(), &ut) != 0) {
|
|
kWarning() << QString::fromLatin1("Couldn't preserve access and modification time for\n%1").arg(dest);
|
|
}
|
|
|
|
processedSize(buff_src.st_size);
|
|
finished();
|
|
}
|
|
|
|
void FileProtocol::listDir(const KUrl &url)
|
|
{
|
|
if (!url.isLocalFile()) {
|
|
KUrl redir(url);
|
|
redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "sftp"));
|
|
redirection(redir);
|
|
kDebug(7101) << "redirecting to " << redir.url();
|
|
finished();
|
|
return;
|
|
}
|
|
|
|
const QString path(url.toLocalFile());
|
|
const QByteArray _path(QFile::encodeName(path));
|
|
DIR* dp = opendir(_path.data());
|
|
if (dp == 0) {
|
|
switch (errno) {
|
|
case ENOENT: {
|
|
error(KIO::ERR_DOES_NOT_EXIST, path);
|
|
return;
|
|
}
|
|
case ENOTDIR: {
|
|
error(KIO::ERR_IS_FILE, path);
|
|
break;
|
|
}
|
|
#ifdef ENOMEDIUM
|
|
case ENOMEDIUM: {
|
|
error(ERR_SLAVE_DEFINED, i18n("No media in device for %1", path));
|
|
break;
|
|
}
|
|
#endif
|
|
default: {
|
|
error(KIO::ERR_CANNOT_ENTER_DIRECTORY, path);
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const QString sDetails = metaData(QLatin1String("details"));
|
|
const int details = (sDetails.isEmpty() ? 2 : sDetails.toInt());
|
|
//kDebug(7101) << "========= LIST " << url << "details=" << details << " =========";
|
|
UDSEntry entry;
|
|
|
|
KDE_struct_dirent *ep;
|
|
while ((ep = KDE_readdir(dp)) != 0 ) {
|
|
entry.clear();
|
|
|
|
const QString filename = QFile::decodeName(ep->d_name);
|
|
QString filepath = path;
|
|
if (!filepath.endsWith(QDir::separator())) {
|
|
filepath += QDir::separator();
|
|
}
|
|
filepath += filename;
|
|
if (createUDSEntry(filename, filepath, entry, details)) {
|
|
listEntry(entry, false);
|
|
}
|
|
}
|
|
|
|
closedir(dp);
|
|
listEntry(entry, true); // ready
|
|
|
|
finished();
|
|
}
|
|
|
|
void FileProtocol::rename(const KUrl &srcUrl, const KUrl &destUrl, KIO::JobFlags _flags)
|
|
{
|
|
const QString src = srcUrl.toLocalFile();
|
|
const QString dest = destUrl.toLocalFile();
|
|
const QByteArray _src(QFile::encodeName(src));
|
|
const QByteArray _dest(QFile::encodeName(dest));
|
|
KDE_struct_stat buff_src;
|
|
if (KDE_lstat(_src.data(), &buff_src) == -1) {
|
|
if (errno == EACCES) {
|
|
error(KIO::ERR_ACCESS_DENIED, src);
|
|
} else {
|
|
error(KIO::ERR_DOES_NOT_EXIST, src);
|
|
}
|
|
return;
|
|
}
|
|
|
|
KDE_struct_stat buff_dest;
|
|
// stat symlinks here (lstat, not stat), to avoid ERR_IDENTICAL_FILES when replacing symlink
|
|
// with its target (#169547)
|
|
bool dest_exists = (KDE_lstat(_dest.data(), &buff_dest) != -1);
|
|
if (dest_exists) {
|
|
if (same_inode(buff_dest, buff_src)) {
|
|
error(KIO::ERR_IDENTICAL_FILES, dest);
|
|
return;
|
|
}
|
|
|
|
if (S_ISDIR(buff_dest.st_mode)) {
|
|
error(KIO::ERR_DIR_ALREADY_EXIST, dest);
|
|
return;
|
|
}
|
|
|
|
if (!(_flags & KIO::Overwrite)) {
|
|
error(KIO::ERR_FILE_ALREADY_EXIST, dest);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (KDE_rename(_src.data(), _dest.data()) == -1) {
|
|
if (errno == EACCES || errno == EPERM) {
|
|
error(KIO::ERR_ACCESS_DENIED, dest);
|
|
} else if (errno == EXDEV) {
|
|
error(KIO::ERR_UNSUPPORTED_ACTION, QLatin1String("rename"));
|
|
} else if (errno == EROFS) {
|
|
// The file is on a read-only filesystem
|
|
error(KIO::ERR_CANNOT_DELETE, src);
|
|
} else {
|
|
error(KIO::ERR_CANNOT_RENAME, src);
|
|
}
|
|
return;
|
|
}
|
|
|
|
finished();
|
|
}
|
|
|
|
void FileProtocol::symlink(const QString &target, const KUrl &destUrl, KIO::JobFlags flags)
|
|
{
|
|
const QString dest = destUrl.toLocalFile();
|
|
// Assume dest is local too (wouldn't be here otherwise)
|
|
if (::symlink(QFile::encodeName(target), QFile::encodeName(dest)) == -1) {
|
|
// Does the destination already exist ?
|
|
if (errno == EEXIST) {
|
|
if (flags & KIO::Overwrite) {
|
|
// Try to delete the destination
|
|
if (unlink(QFile::encodeName(dest)) != 0) {
|
|
error(KIO::ERR_CANNOT_DELETE, dest);
|
|
return;
|
|
}
|
|
// Try again - this won't loop forever since unlink succeeded
|
|
symlink(target, destUrl, flags);
|
|
} else {
|
|
KDE_struct_stat buff_dest;
|
|
if (KDE_lstat(QFile::encodeName(dest), &buff_dest) == 0 && S_ISDIR(buff_dest.st_mode)) {
|
|
error(KIO::ERR_DIR_ALREADY_EXIST, dest);
|
|
} else {
|
|
error(KIO::ERR_FILE_ALREADY_EXIST, dest);
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
// Some error occurred while we tried to symlink
|
|
error(KIO::ERR_CANNOT_SYMLINK, dest);
|
|
return;
|
|
}
|
|
}
|
|
finished();
|
|
}
|
|
|
|
void FileProtocol::del(const KUrl &url, bool isfile)
|
|
{
|
|
const QString path = url.toLocalFile();
|
|
const QByteArray _path(QFile::encodeName(path));
|
|
|
|
if (isfile) {
|
|
// Delete files
|
|
kDebug(7101) << "Deleting file "<< url;
|
|
|
|
if (unlink(_path.data()) == -1) {
|
|
if (errno == EACCES || errno == EPERM) {
|
|
error(KIO::ERR_ACCESS_DENIED, path);
|
|
} else if (errno == EISDIR) {
|
|
error(KIO::ERR_IS_DIRECTORY, path);
|
|
} else {
|
|
error(KIO::ERR_CANNOT_DELETE, path);
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
// Delete empty directory
|
|
kDebug( 7101 ) << "Deleting directory " << url.url();
|
|
if (metaData(QLatin1String("recurse")) == QLatin1String("true")) {
|
|
if (!deleteRecursive(path)) {
|
|
return;
|
|
}
|
|
}
|
|
if (::rmdir(_path.data()) == -1) {
|
|
if (errno == EACCES || errno == EPERM) {
|
|
error(KIO::ERR_ACCESS_DENIED, path);
|
|
} else {
|
|
kDebug( 7101 ) << "could not rmdir " << ::strerror(errno);
|
|
error(KIO::ERR_COULD_NOT_RMDIR, path);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
finished();
|
|
}
|
|
|
|
void FileProtocol::chown(const KUrl &url, const QString &owner, const QString &group)
|
|
{
|
|
const QString path = url.toLocalFile();
|
|
const QByteArray _path(QFile::encodeName(path));
|
|
uid_t uid;
|
|
gid_t gid;
|
|
|
|
// get uid from given owner
|
|
{
|
|
const KUser kuser(owner);
|
|
if (!kuser.isValid()) {
|
|
error(KIO::ERR_SLAVE_DEFINED, i18n("Could not get user id for given user name %1", owner));
|
|
return;
|
|
}
|
|
uid = kuser.uid();
|
|
}
|
|
|
|
// get gid from given group
|
|
{
|
|
const KUserGroup kusergroup(group);
|
|
if (!kusergroup.isValid()) {
|
|
error(KIO::ERR_SLAVE_DEFINED, i18n( "Could not get group id for given group name %1", group));
|
|
return;
|
|
}
|
|
gid = kusergroup.gid();
|
|
}
|
|
|
|
if (::chown(_path, uid, gid) == -1) {
|
|
switch (errno) {
|
|
case EPERM:
|
|
case EACCES: {
|
|
error(KIO::ERR_ACCESS_DENIED, path);
|
|
break;
|
|
}
|
|
case ENOSPC: {
|
|
error(KIO::ERR_DISK_FULL, path);
|
|
break;
|
|
}
|
|
default: {
|
|
error(KIO::ERR_CANNOT_CHOWN, path);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
finished();
|
|
}
|
|
}
|
|
|
|
void FileProtocol::stat(const KUrl &url)
|
|
{
|
|
if (!url.isLocalFile()) {
|
|
KUrl redir(url);
|
|
redir.setScheme(config()->readEntry("DefaultRemoteProtocol", "sftp"));
|
|
redirection(redir);
|
|
kDebug(7101) << "redirecting to " << redir.url();
|
|
finished();
|
|
return;
|
|
}
|
|
|
|
const QString path(url.path());
|
|
const QString sDetails = metaData(QLatin1String("details"));
|
|
const int details = (sDetails.isEmpty() ? 2 : sDetails.toInt());
|
|
|
|
UDSEntry entry;
|
|
if (!createUDSEntry(url.fileName(), path, entry, details)) {
|
|
error(KIO::ERR_DOES_NOT_EXIST, path);
|
|
return;
|
|
}
|
|
statEntry(entry);
|
|
|
|
finished();
|
|
}
|