summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2016-12-17 09:06:15 +0200
committerSergey Poznyakoff <gray@gnu.org.ua>2016-12-17 11:09:24 +0200
commit9ab204f57de0883f1f876a2b58f3359ab606fd0a (patch)
treefb9dc0faeb3276f9a21b9ea2c0dc919cc69afc12
parentf0461be42ccc00ea751e93abc6eec51e455e925b (diff)
downloadmailutils-9ab204f57de0883f1f876a2b58f3359ab606fd0a.tar.gz
mailutils-9ab204f57de0883f1f876a2b58f3359ab606fd0a.tar.bz2
Improve rename/copy/reomove API.
* examples/rename.c: Remove. * examples/fcopy.c: New file. * examples/fremove.c: New file. * examples/frename.c: New file. * examples/Makefile.am: Update. * include/mailutils/util.h (mu_rename_file): Add flags. (mu_remove_file): New function. (MU_COPY_OVERWRITE): New flag. * libmailutils/base/renamefile.c: New file. * libmailutils/base/Makefile.am: Add newe file. * libmailutils/base/copyfile.c: Fix error handling. * libmailutils/base/renamefile.c (mu_rename_file): Refuse to proceed if the destination file exists and MU_COPY_OVERWRITE flag is not set * libmailutils/diag/errors (MU_ERR_REMOVE_SOURCE) (MU_ERR_RESTORE_META): New errors * imap4d/rename.c (imap4d_rename): Use mu_rename_file * mh/forw.c: Likewise. * mh/mh_whatnow.c: Likewise. * mh/mhn.c: Likewise. * mh/send.c: Likewise. * include/mailutils/cstr.h (mu_str_count): New proto. * include/mailutils/util.h (mu_file_name_is_safe): New proto. * libmailutils/string/safefilename.c: New file. * libmailutils/string/strcount.c: New file. * libmailutils/string/Makefile.am: Update.
-rw-r--r--examples/.gitignore3
-rw-r--r--examples/Makefile.am4
-rw-r--r--examples/fcopy.c63
-rw-r--r--examples/fremove.c49
-rw-r--r--examples/frename.c59
-rw-r--r--examples/rename.c60
-rw-r--r--imap4d/rename.c25
-rw-r--r--include/mailutils/cstr.h2
-rw-r--r--include/mailutils/util.h18
-rw-r--r--libmailutils/base/Makefile.am1
-rw-r--r--libmailutils/base/copyfile.c122
-rw-r--r--libmailutils/base/removefile.c275
-rw-r--r--libmailutils/base/renamefile.c55
-rw-r--r--libmailutils/diag/errors4
-rw-r--r--libmailutils/string/Makefile.am2
-rw-r--r--libmailutils/string/safefilename.c84
-rw-r--r--libmailutils/string/strcount.c50
-rw-r--r--mh/forw.c10
-rw-r--r--mh/mh_whatnow.c8
-rw-r--r--mh/mhn.c26
-rw-r--r--mh/send.c10
21 files changed, 781 insertions, 149 deletions
diff --git a/examples/.gitignore b/examples/.gitignore
index e75f2264a..d4792a31f 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -5,6 +5,9 @@ base64
decode2047
echosrv
encode2047
+fcopy
+fremove
+frename
header
http
iconv
diff --git a/examples/Makefile.am b/examples/Makefile.am
index ca5e0947a..fa63b6624 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -30,6 +30,9 @@ noinst_PROGRAMS = \
addr\
base64\
echosrv\
+ fcopy\
+ fremove\
+ frename\
header\
http\
iconv\
@@ -44,7 +47,6 @@ noinst_PROGRAMS = \
murun\
musocio\
$(NNTPCLIENT)\
- rename\
sa\
sfrom
diff --git a/examples/fcopy.c b/examples/fcopy.c
new file mode 100644
index 000000000..e4aa86852
--- /dev/null
+++ b/examples/fcopy.c
@@ -0,0 +1,63 @@
+#include <mailutils/mailutils.h>
+
+int owner_option;
+int mode_option;
+int force_option;
+int deref_option;
+
+static struct mu_option copy_options[] = {
+ { "owner", 'u', NULL, MU_OPTION_DEFAULT,
+ "copy ownership",
+ mu_c_bool, &owner_option },
+ { "mode", 'm', NULL, MU_OPTION_DEFAULT,
+ "copy mode",
+ mu_c_bool, &mode_option },
+ { "force", 'f', NULL, MU_OPTION_DEFAULT,
+ "force overwriting the destination file if it exists",
+ mu_c_bool, &force_option },
+ { "overwrite", 0, NULL, MU_OPTION_ALIAS },
+ { "dereference", 'h', NULL, MU_OPTION_DEFAULT,
+ "dereference symbolic links",
+ mu_c_bool, &deref_option },
+ MU_OPTION_END
+}, *options[] = { copy_options, NULL };
+
+struct mu_cli_setup cli = {
+ options,
+ NULL,
+ "copy file",
+ "SRC DST"
+};
+
+static char *capa[] = {
+ "debug",
+ NULL
+};
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+ int flags;
+
+ mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv);
+
+ if (argc != 2)
+ {
+ mu_error ("wrong number of arguments");
+ return 1;
+ }
+
+ flags = (owner_option ? MU_COPY_OWNER : 0)
+ | (mode_option ? MU_COPY_MODE : 0)
+ | (force_option ? MU_COPY_OVERWRITE : 0)
+ | (deref_option ? MU_COPY_DEREF : 0);
+ rc = mu_copy_file (argv[0], argv[1], flags);
+
+ if (rc)
+ mu_diag_funcall (MU_DIAG_ERROR, "mu_copy_file", NULL, rc);
+
+ return !!rc;
+}
+
+
diff --git a/examples/fremove.c b/examples/fremove.c
new file mode 100644
index 000000000..e7c2d9118
--- /dev/null
+++ b/examples/fremove.c
@@ -0,0 +1,49 @@
+#include <mailutils/mailutils.h>
+
+int owner_option;
+int mode_option;
+int force_option;
+
+static struct mu_option *options[] = { NULL };
+
+struct mu_cli_setup cli = {
+ options,
+ NULL,
+ "delete file",
+ "FILE"
+};
+
+static char *capa[] = {
+ "debug",
+ NULL
+};
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+
+ mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv);
+
+ if (argc != 1)
+ {
+ mu_error ("wrong number of arguments");
+ return 1;
+ }
+
+ if (!mu_file_name_is_safe (argv[0])
+ || (argv[0][0] == '/' && mu_str_count (argv[0], '/') < 2))
+ {
+ mu_error ("unsafe file name");
+ return 1;
+ }
+
+ rc = mu_remove_file (argv[0]);
+
+ if (rc)
+ mu_diag_funcall (MU_DIAG_ERROR, "mu_remove_file", NULL, rc);
+
+ return !!rc;
+}
+
+
diff --git a/examples/frename.c b/examples/frename.c
new file mode 100644
index 000000000..1bf6556f4
--- /dev/null
+++ b/examples/frename.c
@@ -0,0 +1,59 @@
+#include <mailutils/mailutils.h>
+
+int force_option;
+
+static struct mu_option rename_options[] = {
+ { "force", 'f', NULL, MU_OPTION_DEFAULT,
+ "force overwriting the destination file if it exists",
+ mu_c_bool, &force_option },
+ { "overwrite", 0, NULL, MU_OPTION_ALIAS },
+ MU_OPTION_END
+}, *options[] = { rename_options, NULL };
+
+struct mu_cli_setup cli = {
+ options,
+ NULL,
+ "rename file",
+ "SRC DST"
+};
+
+static char *capa[] = {
+ "debug",
+ NULL
+};
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+
+ mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv);
+
+ if (argc != 2)
+ {
+ mu_error ("wrong number of arguments");
+ return 1;
+ }
+
+ if (!mu_file_name_is_safe (argv[0])
+ || (argv[0][0] == '/' && mu_str_count (argv[0], '/') < 2))
+ {
+ mu_error ("%s: unsafe file name", argv[0]);
+ return 1;
+ }
+ if (!mu_file_name_is_safe (argv[1])
+ || (argv[1][0] == '/' && mu_str_count (argv[1], '/') < 2))
+ {
+ mu_error ("%sunsafe file name", argv[0]);
+ return 1;
+ }
+
+ rc = mu_rename_file (argv[0], argv[1], force_option ? MU_COPY_OVERWRITE : 0);
+
+ if (rc)
+ mu_diag_funcall (MU_DIAG_ERROR, "mu_rename_file", NULL, rc);
+
+ return !!rc;
+}
+
+
diff --git a/examples/rename.c b/examples/rename.c
deleted file mode 100644
index f12264ac7..000000000
--- a/examples/rename.c
+++ /dev/null
@@ -1,60 +0,0 @@
-#include <mailutils/mailutils.h>
-
-int copy_option;
-int owner_option;
-int mode_option;
-
-static struct mu_option rename_options[] = {
- { "copy", 'c', NULL, MU_OPTION_DEFAULT,
- "copy the file",
- mu_c_bool, &copy_option },
- { "owner", 'u', NULL, MU_OPTION_DEFAULT,
- "copy ownership",
- mu_c_bool, &owner_option },
- { "mode", 'm', NULL, MU_OPTION_DEFAULT,
- "copy mode",
- mu_c_bool, &mode_option },
- MU_OPTION_END
-}, *options[] = { rename_options, NULL };
-
-struct mu_cli_setup cli = {
- options,
- NULL,
- "copy or rename file",
- "SRC DST"
-};
-
-static char *capa[] = {
- "debug",
- NULL
-};
-
-int
-main (int argc, char **argv)
-{
- int rc;
-
- mu_cli (argc, argv, &cli, capa, NULL, &argc, &argv);
-
- if (argc != 2)
- {
- mu_error ("wrong number of arguments");
- return 1;
- }
-
- if (copy_option)
- {
- int flags = (owner_option ? MU_COPY_OWNER : 0)
- | (mode_option ? MU_COPY_MODE : 0);
- rc = mu_copy_file (argv[0], argv[1], flags);
- }
- else
- rc = mu_rename_file (argv[0], argv[1]);
-
- if (rc)
- mu_diag_funcall (MU_DIAG_ERROR, "mu_rename_file", NULL, rc);
-
- return !!rc;
-}
-
-
diff --git a/imap4d/rename.c b/imap4d/rename.c
index bca502383..c551d2564 100644
--- a/imap4d/rename.c
+++ b/imap4d/rename.c
@@ -125,8 +125,8 @@ imap4d_rename (struct imap4d_session *session,
if (!newname)
return io_completion_response (command, RESP_NO, "Permission denied");
- /* It is an error to attempt to rename from a mailbox name that already
- exist. */
+ /* It is an error to attempt to rename from a mailbox name that does not
+ exist or to a mailbox name that already exists. */
if (stat (newname, &newst) == 0)
{
/* FIXME: What if it's a maildir?!? */
@@ -216,9 +216,26 @@ imap4d_rename (struct imap4d_session *session,
}
else
{
- if (rename (oldname, newname) != 0)
+ rc = mu_rename_file (oldname, newname, 0);
+ if (rc)
{
- mu_diag_funcall (MU_DIAG_ERROR, "rename", oldname, errno);
+ switch (rc)
+ {
+ case MU_ERR_REMOVE_SOURCE:
+ mu_error (_("failed to remove source mailbox after moving %s to %s"),
+ oldname, newname);
+ break;
+
+ case MU_ERR_RESTORE_META:
+ mu_error (_("failed to restore mailbox ownership/modes after moving %s to %s"),
+ oldname, newname);
+ break;
+
+ default:
+ mu_error (_("error renaming mailbox %s to %s: %s"),
+ oldname, newname, mu_strerror (rc));
+ }
+
rc = RESP_NO;
msg = "Failed";
}
diff --git a/include/mailutils/cstr.h b/include/mailutils/cstr.h
index f98439fd8..579a49686 100644
--- a/include/mailutils/cstr.h
+++ b/include/mailutils/cstr.h
@@ -45,6 +45,8 @@ char *mu_str_skip_cset_comp (const char *str, const char *cset);
char *mu_str_stripws (char *string);
int mu_string_split (const char *string, char *delim, mu_list_t list);
+
+size_t mu_str_count (char const *str, int chr);
#ifdef __cplusplus
}
diff --git a/include/mailutils/util.h b/include/mailutils/util.h
index 23792e26f..d539099b5 100644
--- a/include/mailutils/util.h
+++ b/include/mailutils/util.h
@@ -140,8 +140,6 @@ struct mu_param
char *name;
char *value;
};
-
-
int mu_content_type_parse (const char *input, mu_content_type_t *retct);
void mu_content_type_destroy (mu_content_type_t *pptr);
@@ -207,17 +205,23 @@ int mu_str_to_c (char const *string, mu_c_type_t type, void *tgt,
/* -------------------------- */
/* Safe file copy and rename */
/* -------------------------- */
-#define MU_COPY_MODE 0x01
-#define MU_COPY_OWNER 0x02
-#define MU_COPY_SYMLINK 0x04
-#define MU_COPY_FORCE 0x08
+/* Bits for the flags argument of mu_copy_file and mu_rename_file. The
+ MU_COPY_OVERWRITE is valid for both calls. The rest is for mu_copy_file
+ only */
+#define MU_COPY_OVERWRITE 0x01 /* Overwrite destination file, if it exists */
+#define MU_COPY_MODE 0x02 /* Preserve file mode */
+#define MU_COPY_OWNER 0x04 /* Preserve file ownership */
+#define MU_COPY_DEREF 0x08 /* Dereference the source file */
int mu_copy_file (const char *srcpath, const char *dstpath, int flags);
-int mu_rename_file (const char *oldpath, const char *newpath);
+int mu_rename_file (const char *oldpath, const char *newpath, int flags);
+int mu_remove_file (const char *path);
/* ----------------------- */
/* Assorted functions. */
/* ----------------------- */
+int mu_file_name_is_safe (char const *str);
+
int mu_getmaxfd (void);
/* Get the host name, doing a gethostbyname() if possible. */
int mu_get_host_name (char **host);
diff --git a/libmailutils/base/Makefile.am b/libmailutils/base/Makefile.am
index e16fb4489..24f08ba44 100644
--- a/libmailutils/base/Makefile.am
+++ b/libmailutils/base/Makefile.am
@@ -59,6 +59,7 @@ libbase_la_SOURCES = \
registrar.c\
refcount.c\
renamefile.c\
+ removefile.c\
rfc2047.c\
schemeauto.c\
sha1.c\
diff --git a/libmailutils/base/copyfile.c b/libmailutils/base/copyfile.c
index f65d59c0e..ff03a4b98 100644
--- a/libmailutils/base/copyfile.c
+++ b/libmailutils/base/copyfile.c
@@ -1,3 +1,19 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 2016 Free Software Foundation, Inc.
+
+ GNU Mailutils 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 3, or (at your option)
+ any later version.
+
+ GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
+
#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -16,13 +32,24 @@ static int copy_regular_file (const char *srcpath, const char *dstpath,
static int copy_symlink (const char *srcpath, const char *dstpath);
static int copy_dir (const char *srcpath, const char *dstpath, int flags);
+/* Copy SRCPATH to DSTPATH. SRCPATH can be any kind of file. If it is
+ a directory, its content will be copied recursively.
+
+ FLAGS:
+
+ MU_COPY_OVERWRITE Overwrite destination file, if it exists.
+ MU_COPY_MODE Preserve file mode
+ MU_COPY_OWNER Preserve file ownership
+ MU_COPY_DEREF Dereference symbolic links: operate on files they
+ refer to.
+*/
int
mu_copy_file (const char *srcpath, const char *dstpath, int flags)
{
int rc = 0;
struct stat st;
- if (((flags & MU_COPY_SYMLINK) ? lstat : stat) (srcpath, &st))
+ if (((flags & MU_COPY_DEREF) ? stat : lstat) (srcpath, &st))
{
mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
(_("can't stat file %s: %s"),
@@ -30,6 +57,23 @@ mu_copy_file (const char *srcpath, const char *dstpath, int flags)
return errno;
}
+ if (access (dstpath, F_OK) == 0)
+ {
+ if (flags & MU_COPY_OVERWRITE)
+ {
+ rc = mu_remove_file (dstpath);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't remove destination %s: %s"),
+ dstpath, mu_strerror (rc)));
+ return rc;
+ }
+ }
+ else
+ return EEXIST;
+ }
+
switch (st.st_mode & S_IFMT)
{
case S_IFREG:
@@ -37,11 +81,9 @@ mu_copy_file (const char *srcpath, const char *dstpath, int flags)
case S_IFLNK:
return copy_symlink (srcpath, dstpath);
- break;
case S_IFDIR:
return copy_dir (srcpath, dstpath, flags);
- break;
case S_IFBLK:
case S_IFCHR:
@@ -123,16 +165,16 @@ copy_regular_file (const char *srcpath, const char *dstpath, int flags,
{
if (fchmod ((int) trans[0], mode))
{
- rc = errno;
mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
(_("%s: cannot chmod: %s"),
- dstpath, mu_strerror (rc)));
+ dstpath, mu_strerror (errno)));
+ rc = MU_ERR_RESTORE_META;
}
else if (flags & MU_COPY_OWNER)
{
uid_t uid;
gid_t gid;
-
+
if (getuid () == 0)
{
uid = st->st_uid;
@@ -153,13 +195,13 @@ copy_regular_file (const char *srcpath, const char *dstpath, int flags,
{
if (fchown ((int) trans[0], uid, gid))
{
- rc = errno;
mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
(_("%s: cannot chown to %lu.%lu: %s"),
dstpath,
(unsigned long) uid,
(unsigned long) gid,
- mu_strerror (rc)));
+ mu_strerror (errno)));
+ rc = MU_ERR_RESTORE_META;
}
}
}
@@ -212,9 +254,8 @@ copy_dir (const char *srcpath, const char *dstpath, int flags)
{
DIR *dirp;
struct dirent *dp;
- struct stat st, st1;
+ struct stat st;
int rc;
- int create = 0;
mode_t mode, mask;
if (stat (srcpath, &st))
@@ -225,67 +266,28 @@ copy_dir (const char *srcpath, const char *dstpath, int flags)
return errno;
}
- if (stat (dstpath, &st1))
- {
- if (errno == ENOENT)
- create = 1;
- else
- {
- mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
- (_("can't stat directory %s: %s"),
- dstpath, mu_strerror (errno)));
- return errno;
- }
- }
- else if (!S_ISDIR (st1.st_mode))
- {
- if (flags & MU_COPY_FORCE)
- {
- if (unlink (dstpath))
- {
- rc = errno;
- mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
- (_("%s is not a directory and cannot be unlinked: %s"),
- dstpath, mu_strerror (rc)));
- return rc;
- }
- create = 1;
- }
- else
- {
- mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
- (_("%s is not a directory"),
- dstpath));
- return EEXIST;
- }
- }
-
mask = umask (077);
mode = ((flags & MU_COPY_MODE) ? st.st_mode : (0777 & ~mask)) & 0777;
- if (create)
- {
- rc = mkdir (dstpath, 0700);
- umask (mask);
+ rc = mkdir (dstpath, 0700);
+ umask (mask);
- if (rc)
- {
- mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
- (_("can't create directory %s: %s"),
- dstpath, mu_strerror (errno)));
- return errno;
- }
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't create directory %s: %s"),
+ dstpath, mu_strerror (errno)));
+ return errno;
}
- else
- umask (mask);
dirp = opendir (srcpath);
if (dirp == NULL)
{
- mu_debug (MU_DEBCAT_FOLDER, MU_DEBUG_ERROR,
+ rc = errno;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
("cannot open directory %s: %s",
srcpath, mu_strerror (errno)));
- return 1;
+ return rc;
}
while ((dp = readdir (dirp)))
diff --git a/libmailutils/base/removefile.c b/libmailutils/base/removefile.c
new file mode 100644
index 000000000..405eb7ba0
--- /dev/null
+++ b/libmailutils/base/removefile.c
@@ -0,0 +1,275 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 2016 Free Software Foundation, Inc.
+
+ GNU Mailutils 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 3, or (at your option)
+ any later version.
+
+ GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <mailutils/stream.h>
+#include <mailutils/util.h>
+#include <mailutils/diag.h>
+#include <mailutils/error.h>
+#include <mailutils/errno.h>
+#include <mailutils/list.h>
+#include <mailutils/iterator.h>
+#include <mailutils/nls.h>
+
+static int removedir (const char *path);
+
+int
+mu_remove_file (const char *path)
+{
+ int rc = 0;
+ struct stat st;
+
+ if (stat (path, &st))
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't stat file %s: %s"),
+ path, mu_strerror (errno)));
+ return errno;
+ }
+
+ if (S_ISDIR (st.st_mode))
+ rc = removedir (path);
+ else if (unlink (path))
+ {
+ rc = errno;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't unlink file %s: %s"),
+ path, mu_strerror (rc)));
+ }
+
+ return rc;
+}
+
+struct nameent
+{
+ int isdir;
+ char name[1];
+};
+
+static int
+name_add (mu_list_t list, char const *name)
+{
+ int rc;
+ size_t len = strlen (name);
+ struct nameent *ent = malloc (sizeof (*ent) + len);
+ if (!ent)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("%s", mu_strerror (errno)));
+ return 1;
+ }
+ ent->isdir = -1;
+ strcpy (ent->name, name);
+ rc = mu_list_append (list, ent);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("mu_list_append: %s", mu_strerror (rc)));
+ free (ent);
+ }
+
+ return rc;
+}
+
+static int
+lsdir (const char *path, mu_list_t list)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int rc = 0;
+
+ dirp = opendir (path);
+ if (dirp == NULL)
+ {
+ rc = errno;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("cannot open directory %s: %s",
+ path, mu_strerror (errno)));
+ return rc;
+ }
+
+ while ((dp = readdir (dirp)))
+ {
+ char const *ename = dp->d_name;
+ char *filename;
+
+ if (ename[ename[0] != '.' ? 0 : ename[1] != '.' ? 1 : 2] == 0)
+ continue;
+
+ filename = mu_make_file_name (path, ename);
+ if (!filename)
+ {
+ rc = errno;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("%s: can't create file name: %s",
+ path, mu_strerror (errno)));
+ break;
+ }
+
+ rc = name_add (list, filename);
+ free (filename);
+ if (rc)
+ break;
+ }
+
+ closedir (dirp);
+
+ return rc;
+}
+
+static int
+namecmp (const void *a, const void *b)
+{
+ struct nameent const *enta = a;
+ struct nameent const *entb = b;
+ int d = enta->isdir - entb->isdir;
+ if (d)
+ return d;
+ return strcmp (entb->name, enta->name);
+}
+
+static int
+check_parent_access (const char *path)
+{
+ int rc;
+ char *name, *p;
+
+ name = strdup (path);
+ if (!name)
+ return errno;
+ p = strrchr (name, '/');
+ if (p)
+ *p = 0;
+ else
+ strcpy (name, ".");
+ rc = access (name, R_OK|W_OK|X_OK);
+ free (name);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("not enough privileges to remove files from %s"),
+ name));
+ return EACCES;
+ }
+
+ return 0;
+}
+
+static int
+removedir (const char *path)
+{
+ int rc;
+ struct stat st;
+ mu_list_t namelist;
+ mu_iterator_t itr;
+ struct nameent *ent;
+
+ rc = check_parent_access (path);
+ if (rc)
+ return rc;
+
+ rc = mu_list_create (&namelist);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("mu_list_create: %s", mu_strerror (rc)));
+ return rc;
+ }
+ mu_list_set_destroy_item (namelist, mu_list_free_item);
+ mu_list_set_comparator (namelist, namecmp);
+
+ rc = name_add (namelist, path);
+ if (rc)
+ {
+ mu_list_destroy (&namelist);
+ return rc;
+ }
+
+ rc = mu_list_get_iterator (namelist, &itr);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ ("mu_list_get_iterator: %s", mu_strerror (rc)));
+ mu_list_destroy (&namelist);
+ return rc;
+ }
+
+ for (mu_iterator_first (itr);
+ !mu_iterator_is_done (itr);
+ mu_iterator_next (itr))
+ {
+ mu_iterator_current (itr, (void **)&ent);
+
+ if (lstat (ent->name, &st))
+ {
+ rc = errno;
+ if (rc == ENOENT)
+ continue;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't lstat file %s: %s"),
+ ent->name, mu_strerror (rc)));
+ break;
+ }
+
+ if (S_ISDIR (st.st_mode))
+ {
+ ent->isdir = 1;
+ if (access (ent->name, R_OK|W_OK|X_OK))
+ {
+ rc = EACCES;
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("not enough privileges to remove files from %s"),
+ ent->name));
+ }
+ else
+ rc = lsdir (ent->name, namelist);
+ if (rc)
+ break;
+ }
+ else
+ ent->isdir = 0;
+ }
+
+ if (rc == 0)
+ {
+ mu_list_sort (namelist, namecmp);
+
+ for (mu_iterator_first (itr);
+ !mu_iterator_is_done (itr);
+ mu_iterator_next (itr))
+ {
+ mu_iterator_current (itr, (void **)&ent);
+ rc = (ent->isdir ? rmdir : unlink) (ent->name);
+ if (rc)
+ {
+ mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
+ (_("can't remove %s: %s"),
+ ent->name, mu_strerror (rc)));
+ }
+ }
+ }
+ mu_iterator_destroy (&itr);
+ mu_list_destroy (&namelist);
+
+ return rc;
+}
+
+
diff --git a/libmailutils/base/renamefile.c b/libmailutils/base/renamefile.c
index 5bc0befa9..a791619af 100644
--- a/libmailutils/base/renamefile.c
+++ b/libmailutils/base/renamefile.c
@@ -1,6 +1,23 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 2016 Free Software Foundation, Inc.
+
+ GNU Mailutils 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 3, or (at your option)
+ any later version.
+
+ GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
+
#include <config.h>
#include <stdio.h>
#include <unistd.h>
+#include <sys/stat.h>
#include <mailutils/stream.h>
#include <mailutils/util.h>
#include <mailutils/diag.h>
@@ -8,10 +25,36 @@
#include <mailutils/errno.h>
#include <mailutils/nls.h>
+/* Rename file OLDPATH to NEWPATH. Prefer rename(2), unless OLDPATH
+ and NEWPATH reside on different devices. In the latter case, fall
+ back to recursive copying.
+
+ FLAGS controls what to do if NEWPATH exists. If it has MU_COPY_OVERWRITE
+ bit set, the NEWPATH will be overwritten (if possible, atomically).
+ Otherwise EEXIST will be returned.
+*/
int
-mu_rename_file (const char *oldpath, const char *newpath)
+mu_rename_file (const char *oldpath, const char *newpath, int flags)
{
int rc;
+ struct stat st;
+
+ if (access (oldpath, F_OK))
+ return errno;
+
+ if (stat (newpath, &st) == 0)
+ {
+ if (flags & MU_COPY_OVERWRITE)
+ {
+ if (S_ISDIR (st.st_mode))
+ {
+ if (mu_remove_file (newpath))
+ return MU_ERR_REMOVE_DEST;
+ }
+ }
+ else
+ return EEXIST;
+ }
if (rename (oldpath, newpath) == 0)
return 0;
@@ -24,16 +67,20 @@ mu_rename_file (const char *oldpath, const char *newpath)
mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_TRACE1,
(_("attempting copy")));
- rc = mu_copy_file (oldpath, newpath, MU_COPY_MODE|MU_COPY_OWNER);
+ rc = mu_copy_file (oldpath, newpath, flags|MU_COPY_MODE|MU_COPY_OWNER);
if (rc == 0)
{
- if (unlink (oldpath))
+ rc = mu_remove_file (oldpath);
+ if (rc)
{
mu_debug (MU_DEBCAT_STREAM, MU_DEBUG_ERROR,
(_("copied %s to %s, but failed to remove the source: %s"),
- oldpath, newpath, mu_strerror (errno)));
+ oldpath, newpath, mu_strerror (rc)));
+ rc = MU_ERR_REMOVE_SOURCE;
}
}
}
+ else
+ rc = errno;
return rc;
}
diff --git a/libmailutils/diag/errors b/libmailutils/diag/errors
index 4b394f442..c4fec20e1 100644
--- a/libmailutils/diag/errors
+++ b/libmailutils/diag/errors
@@ -125,3