]> git.ipfire.org Git - people/ms/dma.git/commitdiff
Initial import of a simple utility to convert dma spool files between formats.
authorPeter Pentchev <roam@ringlet.net>
Tue, 15 Jun 2010 10:29:41 +0000 (10:29 +0000)
committerPeter Pentchev <roam@ringlet.net>
Tue, 15 Jun 2010 10:29:41 +0000 (10:29 +0000)
migrate/.depend [new file with mode: 0644]
migrate/Makefile [new file with mode: 0644]
migrate/NEWS [new file with mode: 0644]
migrate/dma-migrate [new file with mode: 0755]
migrate/dma-migrate.8 [new file with mode: 0644]
migrate/dma-migrate.c [new file with mode: 0644]

diff --git a/migrate/.depend b/migrate/.depend
new file mode 100644 (file)
index 0000000..d78f1b9
--- /dev/null
@@ -0,0 +1,29 @@
+dma-migrate.o dma-migrate.ln: dma-migrate.c /usr/include/sys/types.h \
+ /usr/include/features.h /usr/include/bits/predefs.h \
+ /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
+ /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
+ /usr/include/bits/types.h /usr/include/bits/typesizes.h \
+ /usr/include/time.h /usr/lib/gcc/i486-linux-gnu/4.4.4/include/stddef.h \
+ /usr/include/endian.h /usr/include/bits/endian.h \
+ /usr/include/bits/byteswap.h /usr/include/sys/select.h \
+ /usr/include/bits/select.h /usr/include/bits/sigset.h \
+ /usr/include/bits/time.h /usr/include/sys/sysmacros.h \
+ /usr/include/bits/pthreadtypes.h /usr/include/sys/file.h \
+ /usr/include/fcntl.h /usr/include/bits/fcntl.h /usr/include/bits/uio.h \
+ /usr/include/sys/stat.h /usr/include/bits/stat.h /usr/include/dirent.h \
+ /usr/include/bits/dirent.h /usr/include/bits/posix1_lim.h \
+ /usr/include/bits/local_lim.h /usr/include/linux/limits.h \
+ /usr/include/err.h /usr/lib/gcc/i486-linux-gnu/4.4.4/include/stdarg.h \
+ /usr/include/errno.h /usr/include/bits/errno.h \
+ /usr/include/linux/errno.h /usr/include/asm/errno.h \
+ /usr/include/asm-generic/errno.h /usr/include/asm-generic/errno-base.h \
+ /usr/include/inttypes.h /usr/include/stdint.h /usr/include/bits/wchar.h \
+ /usr/include/regex.h /usr/include/gnu/option-groups.h \
+ /usr/include/stdio.h /usr/include/libio.h /usr/include/_G_config.h \
+ /usr/include/wchar.h /usr/include/bits/stdio_lim.h \
+ /usr/include/bits/sys_errlist.h /usr/include/stdlib.h \
+ /usr/include/bits/waitflags.h /usr/include/bits/waitstatus.h \
+ /usr/include/xlocale.h /usr/include/alloca.h /usr/include/string.h \
+ /usr/include/unistd.h /usr/include/bits/posix_opt.h \
+ /usr/include/bits/environments.h /usr/include/bits/confname.h \
+ /usr/include/getopt.h
diff --git a/migrate/Makefile b/migrate/Makefile
new file mode 100644 (file)
index 0000000..74163fb
--- /dev/null
@@ -0,0 +1,7 @@
+PROG=  dma-migrate
+SRCS=  dma-migrate.c
+MAN=   dma-migrate.8
+
+WARNS?=        6
+
+.include <bsd.prog.mk>
diff --git a/migrate/NEWS b/migrate/NEWS
new file mode 100644 (file)
index 0000000..29dca0d
--- /dev/null
@@ -0,0 +1,6 @@
+Change log for dma-migrate, the DragonFly Mail Agent queue migration utility.
+
+0.01   not yet ;)
+       - first public release
+
+Comments: Peter Pentchev <roam@ringlet.net>
diff --git a/migrate/dma-migrate b/migrate/dma-migrate
new file mode 100755 (executable)
index 0000000..af3f9b9
Binary files /dev/null and b/migrate/dma-migrate differ
diff --git a/migrate/dma-migrate.8 b/migrate/dma-migrate.8
new file mode 100644 (file)
index 0000000..e40acef
--- /dev/null
@@ -0,0 +1,98 @@
+.\" Copyright (c) 2010  Peter Pentchev
+.\" 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+.\"
+.Dd May 11, 2009
+.Dt dma-migrate 8
+.Os
+.Sh NAME
+.Nm dma-migrate
+.Nd convert the DragonFly Mail Agent's queue files
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl d Ar spooldir
+.Nm
+.Op Fl h | Fl V
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to convert the mail queue files in the
+.Xr dma 8
+spool directory to the latest spool directory format supported by
+the installed version of
+.Xr dma 8 .
+Currently it only handles the conversion from a single file containing
+both message and delivery metadata to the M/Q format.
+.Pp
+The following command-line options are available:
+.Bl -tag -width indent
+.It Fl d
+Specify the location of the
+.Xr dma 8
+spool directory, default
+.Pa /var/spool/dma .
+.It Fl h
+Display usage information and exit.
+.It Fl V
+Display program version information and exit.
+.It Fl v
+Verbose output - display diagnostic messages.
+.El
+.Sh ENVIRONMENT
+The operation of the
+.Nm
+utility is currently not influenced by environment variables.
+.Sh FILES
+The
+.Nm
+utility looks for the
+.Xr dma 8
+mail queue files in the
+.Pa /var/spool/dma
+directory, unless another location is specified by the
+.Fl d
+command-line option.
+.Sh EXIT STATUS
+The
+.Nm
+utility will scan the whole spool directory and attempt to convert all
+suitable files there.
+If there are no files to be converted, or if all the conversions are
+successful, it will complete with an exit code of zero.
+If any conversion errors are encountered, a message will be displayed
+to the standard error stream and
+.Nm
+will exit with an exit code of 1 after attempting to convert the rest of
+the suitable files in the spool directory.
+.Sh SEE ALSO
+.Xr dma 8
+.Sh STANDARDS
+No standards documentation was harmed in the process of creating
+.Nm .
+.Sh BUGS
+Please report any bugs in
+.Nm
+to the author.
+.Sh AUTHOR
+.An Peter Pentchev Aq roam@ringlet.net
diff --git a/migrate/dma-migrate.c b/migrate/dma-migrate.c
new file mode 100644 (file)
index 0000000..3f21b8b
--- /dev/null
@@ -0,0 +1,411 @@
+/*-
+ * Copyright (c) 2010  Peter Pentchev
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef __printflike
+#ifdef __GNUC__
+#define __printflike(fmtarg, firstvararg)      \
+       __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#else
+#define __printflike(fmtarg, firstvararg)
+#endif
+#endif
+
+#define DEFAULT_SPOOLDIR       "/var/spool/dma"
+
+static int      verbose = 0;
+static char     copybuf[BUFSIZ];
+
+static int      dma_migrate(int, const char *);
+
+static int      open_locked(const char *, int, ...);
+static void     cleanup_file(int, char *);
+
+static void     usage(int);
+static void     version(void);
+static void     debug(const char *, ...) __printflike(1, 2);
+
+int
+main(int argc, char **argv)
+{
+       const char *spooldir;
+       int hflag, Vflag, errs, fd, res;
+       char ch;
+       DIR *d;
+       struct dirent *e;
+       struct stat sb;
+
+        srandom((unsigned long)((time(NULL) ^ getpid()) + ((uintptr_t)argv)));
+
+       hflag = Vflag = 0;
+       spooldir = DEFAULT_SPOOLDIR;
+       while (ch = getopt(argc, argv, "d:hVv"), ch != -1)
+               switch (ch) {
+                       case 'd':
+                               spooldir = optarg;
+                               break;
+
+                       case 'h':
+                               hflag = 1;
+                               break;
+
+                       case 'V':
+                               Vflag = 1;
+                               break;
+
+                       case 'v':
+                               verbose = 1;
+                               break;
+
+                       case '?':
+                               usage(1);
+               }
+       if (hflag)
+               usage(0);
+       if (Vflag)
+               version();
+       if (hflag || Vflag)
+               exit(0);
+
+       argc -= optind;
+       argv += optind;
+
+       /* Let's roll! */
+       if (chdir(spooldir) == -1)
+               err(1, "Could not change into spool directory %s", spooldir);
+       if (d = opendir("."), d == NULL)
+               err(1, "Could not read spool directory %s", spooldir);
+       errs = 0;
+       while (e = readdir(d), e != NULL) {
+               /* Do we care about this entry? */
+               debug("Read a directory entry: %s\n", e->d_name);
+               if (strncmp(e->d_name, "tmp_", 4) == 0 ||
+                   e->d_name[0] == 'M' || e->d_name[0] == 'Q' ||
+                   (e->d_type != DT_REG && e->d_type != DT_UNKNOWN))
+                       continue;
+               if (e->d_type == DT_UNKNOWN)
+                       if (stat(e->d_name, &sb) == -1 || !S_ISREG(sb.st_mode))
+                               continue;
+               debug("- want to process it\n");
+
+               /* Try to lock it - skip it if dma is delivering the message */
+               if (fd = open_locked(e->d_name, O_RDONLY|O_NDELAY), fd == -1) {
+                       debug("- seems to be locked, skipping\n");
+                       continue;
+               }
+
+               /* Okay, convert it to the M/Q schema */
+               res = dma_migrate(fd, e->d_name);
+               close(fd);
+               if (res == -1)
+                       errs++;
+       }
+       if (errs)
+               debug("Finished, %d conversion errors\n", errs);
+       else
+               debug("Everything seems to be all right\n");
+       return (errs && 1);
+}
+
+static int
+dma_migrate(int fd, const char *fname)
+{
+       const char *id;
+       char *mname, *qname, *tempname, *sender, *recp, *line, *recpline;
+       int mfd, qfd, tempfd;
+       struct stat sb;
+       FILE *fp, *qfp, *mfp;
+       size_t sz, len;
+       static regex_t *qidreg = NULL;
+
+       mfd = tempfd = qfd = -1;
+       mname = qname = sender = recp = line = NULL;
+       fp = qfp = NULL;
+
+       if (fstat(fd, &sb) == -1) {
+               warn("Could not fstat(%s)", fname);
+               return (-1);
+       }
+       /*
+        * Let's just blithely assume that the queue ID *is* the filename,
+        * since that's the way dma did things so far.
+        * Well, okay, let's check it.
+        */
+       if (qidreg == NULL) {
+               regex_t *nreg;
+
+               if ((nreg = malloc(sizeof(*qidreg))) == NULL) {
+                       warn("Could not allocate memory for a regex");
+                       return (-1);
+               }
+               if (regcomp(nreg, "^[a-fA-F0-9]\\+\\.[a-fA-F0-9]\\+$", 0)
+                   != 0) {
+                       warnx("Could not compile a dma queue ID regex");
+                       free(nreg);
+                       return (-1);
+               }
+               qidreg = nreg;
+       }
+       if (regexec(qidreg, fname, 0, NULL, 0) != 0) {
+               warnx("The name '%s' is not a valid dma queue ID", fname);
+               return (-1);
+       }
+       id = fname;
+       debug("  - queue ID %s\n", id);
+       if (asprintf(&mname, "M%s", id) == -1 ||
+           asprintf(&tempname, "tmp_%s", id) == -1 ||
+           asprintf(&qname, "Q%s", id) == -1 ||
+           mname == NULL || tempname == NULL || qname == NULL)
+               goto fail;
+
+       /* Create the message placeholder early to avoid races */
+       mfd = open_locked(mname, O_CREAT | O_EXCL | O_RDWR, 0600);
+       if (mfd == -1) {
+               warn("Could not create temporary file %s", mname);
+               goto fail;
+       }
+       if (stat(qname, &sb) != -1 || errno != ENOENT ||
+           stat(tempname, &sb) != -1 || errno != ENOENT) {
+               warnx("Some of the queue files for %s already exist", fname);
+               goto fail;
+       }
+       debug("  - mfd %d names %s, %s, %s\n", mfd, mname, tempname, qname);
+
+       fp = fdopen(fd, "r");
+       if (fp == NULL) {
+               warn("Could not reopen the descriptor for %s", fname);
+               goto fail;
+       }
+
+       /* Parse the header of the old-format message file */
+       /* ...sender... */
+       if (getline(&sender, &sz, fp) == -1) {
+               warn("Could not read the initial line from %s", fname);
+               goto fail;
+       }
+       sz = strlen(sender);
+       while (sz > 0 && (sender[sz - 1] == '\n' || sender[sz - 1] == '\r'))
+               sender[--sz] = '\0';
+       if (sz == 0) {
+               warnx("Empty sender line in %s", fname);
+               goto fail;
+       }
+       debug("  - sender %s\n", sender);
+       /* ...recipient(s)... */
+       len = strlen(fname);
+       recpline = NULL;
+       while (1) {
+               if (getline(&line, &sz, fp) == -1) {
+                       warn("Could not read a recipient line from %s", fname);
+                       goto fail;
+               }
+               sz = strlen(line);
+               while (sz > 0 &&
+                   (line[sz - 1] == '\n' || line[sz - 1] == '\r'))
+                       line[--sz] = '\0';
+               if (sz == 0) {
+                       free(line);
+                       line = NULL;
+                       break;
+               }
+               if (recp == NULL &&
+                   strncmp(line, fname, len) == 0 && line[len] == ' ') {
+                       recp = line + len + 1;
+                       recpline = line;
+               } else {
+                       free(line);
+               }
+               line = NULL;
+       }
+       if (recp == NULL) {
+               warnx("Could not find its own recipient line in %s", fname);
+               goto fail;
+       }
+       /* ..phew, finished with the header. */
+
+       tempfd = open_locked(tempname, O_CREAT | O_EXCL | O_RDWR, 0600);
+       if (tempfd == -1) {
+               warn("Could not create a queue file for %s", fname);
+               goto fail;
+       }
+       qfp = fdopen(tempfd, "w");
+       if (qfp == NULL) {
+               warn("Could not fdopen(%s) for %s", tempname, fname);
+               goto fail;
+       }
+       mfp = fdopen(mfd, "w");
+       if (mfp == NULL) {
+               warn("Could not fdopen(%s) for %s", mname, fname);
+               goto fail;
+       }
+       fprintf(qfp, "ID: %s\nSender: %s\nRecipient: %s\n", id, sender, recp);
+       fflush(qfp);
+       fsync(tempfd);
+
+       /* Copy the message file over to mname */
+       while ((sz = fread(copybuf, 1, sizeof(copybuf), fp)) > 0)
+               if (fwrite(copybuf, 1, sz, mfp) != sz) {
+                       warn("Could not copy the message from %s to %s",
+                           fname, mname);
+                       goto fail;
+               }
+       if (ferror(fp)) {
+               warn("Could not read the full message from %s", fname);
+               goto fail;
+       }
+       fflush(mfp);
+       fsync(mfd);
+
+       if (rename(tempname, qname) == -1) {
+               warn("Could not rename the queue file for %s", fname);
+               goto fail;
+       }
+       qfd = tempfd;
+       tempfd = -1;
+       if (unlink(fname) == -1) {
+               warn("Could not remove the old converted file %s", fname);
+               goto fail;
+       }
+
+       fclose(fp);
+       fclose(qfp);
+       free(sender);
+       free(line);
+       free(recpline);
+       free(mname);
+       free(qname);
+       free(tempname);
+       return (0);
+
+fail:
+       if (fp != NULL)
+               fclose(fp);
+       if (qfp != NULL)
+               fclose(qfp);
+       if (sender != NULL)
+               free(sender);
+       if (line != NULL)
+               free(line);
+       if (recpline != NULL)
+               free(recpline);
+       cleanup_file(mfd, mname);
+       cleanup_file(qfd, qname);
+       cleanup_file(tempfd, tempname);
+       return (-1);
+}
+
+static void
+cleanup_file(int fd, char *fname)
+{
+       if (fd != -1) {
+               close(fd);
+               unlink(fname);
+       }
+       if (fname != NULL)
+               free(fname);
+}
+
+static void
+usage(int ferr)
+{
+       const char *s =
+           "Usage:\tdma-migrate [-hVv] [-d spooldir]\n"
+           "\t-d\tspecify the spool directory (" DEFAULT_SPOOLDIR ")\n"
+           "\t-h\tdisplay program usage information and exit\n"
+           "\t-V\tdisplay program version information and exit\n"
+           "\t-v\tverbose operation - display diagnostic messages";
+
+       if (ferr)
+               errx(1, "%s", s);
+       puts(s);
+}
+
+static void
+version(void)
+{
+       printf("dma-migrate 0.01\n");
+}
+
+static void
+debug(const char *fmt, ...)
+{
+       va_list v;
+
+       if (verbose < 1)
+               return;
+       va_start(v, fmt);
+       vfprintf(stderr, fmt, v);
+       va_end(v);
+}
+
+static int
+open_locked(const char *fname, int flags, ...)
+{
+       int mode = 0;
+#ifndef O_EXLOCK
+       int fd, save_errno;
+#endif
+
+       if (flags & O_CREAT) {
+               va_list ap;
+               va_start(ap, flags);
+               mode = va_arg(ap, int);
+               va_end(ap);
+       }
+
+#ifndef O_EXLOCK
+       fd = open(fname, flags, mode);
+       if (fd < 0)
+               return(fd);
+       if (flock(fd, LOCK_EX|((flags & O_NONBLOCK)? LOCK_NB: 0)) < 0) {
+               save_errno = errno;
+               close(fd);
+               errno = save_errno;
+               return(-1);
+       }
+       return(fd);
+#else
+       return(open(fname, flags|O_EXLOCK, mode));
+#endif
+}