[Mew-dist 14657] incm.c

Yasunari Momoi momo at example.com
2000年 10月 19日 (木) 15:10:50 JST


仕事で再び C を使うことになったので,リハビリのために :-)
incdir の C 版を書きました.これもせっかくなので公開しておき
ます.

私のマシンでは 900 通のメール処理で,sh 版より 10 倍ぐらい速
いです.

  incm > /dev/null  0.06s user 1.70s system 94% cpu 1.865 total
  incdir > /dev/null  0.84s user 17.24s system 91% cpu 19.723 total

各種仕様は incdir と同じです.現在は Maildir 形式のみ対応で
す.まだ,エラーチェックとか甘いところもあると思います.

FreeBSD では普通に動いてますが,移植性は全く考慮してません.
configure とか教えてくれる人がいれば,この機会に覚えようか
なぁ,とは思ってますが...(^^;)

# しばらく perl ばっかりだったので,#include と書こうとして
# #! と打ってたりとか,() をつけ忘れたりとか...

-- momo

-------------- next part --------------
/*
 *  incm - incorporate new mail
 *      author: Yasunari Momoi <momo at example.com>
 *      created: 2000/10/19
 *
 *
 *  Add the following to your emacs configuration file.
 *
 *  (setq mew-mailbox-type 'mbox)   ; this applies also maildir
 *  (setq mew-mbox-command "incm")
 *  (setq mew-mbox-command-arg "-d /path/to/your/maildir")
 *
 *
 *  Copyright (C) 2000 Yasunari Momoi.  All rights reserved.
 *  Copyright notice is the same as Mew's one.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>

static char	Version[] = "$Id: incm.c,v 1.2 2000/10/19 05:59:53 momo Exp $";
static char*	Command;

enum BOOL { FALSE, TRUE };

enum MBOXTYPE {
    T_MAILDIR,
    /* T_MBOX, */
};

static char	InboxDir[BUFSIZ];
static char	Mbox[BUFSIZ];
static int	MboxType;
static int	Backup;

void
warning(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
}

void
error(const char *fmt, ...)
{
    va_list ap;

    fprintf(stderr, "error: ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    exit(EXIT_FAILURE);
}

void
usage()
{
    printf("usage: %s [-b] [-d maildir] [-i inboxdir]\n", Command);
    printf("    -b            backup mail to maildir/cur directory\n");
    printf("    -d maildir    path to maildir  (default: %s)\n", Mbox);
    printf("    -i inboxdir   path to inboxdir  (default: %s)\n", InboxDir);
    printf("\n");
    printf("version: %s\n", Version);
    exit(EXIT_FAILURE);
}

void
init_env(int argc, char** argv)
{
    char *home = getenv("HOME");
    Command = argv[0];
    snprintf(InboxDir, sizeof(InboxDir), ".");
    snprintf(Mbox, sizeof(Mbox), "%s/Maildir", home);
    MboxType = T_MAILDIR;
    Backup = FALSE;
}

int
is_number(char* str)
{
    do {
	if (!isdigit(*str))
	    return FALSE;
    } while (*++str != '\0');
    return TRUE;
}

int
get_last_seq()
{
    struct dirent* dp;
    DIR* dirp;
    int last = 0;
    int seq;

    if ((dirp = opendir(InboxDir)) == NULL)
	error("opendir(%s)", InboxDir);
    while ((dp = readdir(dirp)) != NULL) {
	if (!is_number(dp->d_name))
	    continue;
	seq = atoi(dp->d_name);
	last = last > seq ? last : seq;
    }
    closedir(dirp);
    return last;
}

int
compare_string(char** i, char** j)
{
    return strcmp(*i, *j);
}

/*
  fastcopy: delived from mv.c (FreeBSD)
*/
int
fastcopy(char* from, char* to)
{
    struct timeval tval[2];
    static u_int blen;
    static char *bp;
    mode_t oldmode;
    struct stat sb;
    int nread, from_fd, to_fd;

    if (lstat(from, &sb)) {
	warning("lstat(%s)", from);
	return 1;
    }
    if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
	warning("%s", from);
	return 1;
    }
    if (blen < sb.st_blksize) {
	if (bp != NULL)
	    free(bp);
	if ((bp = malloc(sb.st_blksize)) == NULL) {
	    blen = 0;
	    warning("malloc failed");
	    return 1;
	}
	blen = sb.st_blksize;
    }
    while ((to_fd = open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY, 0)) < 0) {
	if (errno == EEXIST && unlink(to) == 0)
	    continue;
	warning("%s", to);
	(void)close(from_fd);
	return 1;
    }
    while ((nread = read(from_fd, bp, blen)) > 0)
	if (write(to_fd, bp, nread) != nread) {
	    warning("%s", to);
	    goto err;
	}
    if (nread < 0) {
	warning("%s", from);
    err:
	if (unlink(to))
	    warning("%s: remove", to);
	(void)close(from_fd);
	(void)close(to_fd);
	return 1;
    }
    (void)close(from_fd);

    oldmode = sb.st_mode & ALLPERMS;
    if (fchown(to_fd, sb.st_uid, sb.st_gid)) {
	warning("%s: set owner/group (was: %lu/%lu)", to,
	     (u_long)sb.st_uid, (u_long)sb.st_gid);
	if (oldmode & (S_ISUID | S_ISGID)) {
	    warning(
		"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
		to, oldmode);
	    sb.st_mode &= ~(S_ISUID | S_ISGID);
	}
    }
    if (fchmod(to_fd, sb.st_mode))
	warning("%s: set mode (was: 0%03o)", to, oldmode);
    /*
     * XXX
     * NFS doesn't support chflags; ignore errors unless there's reason
     * to believe we're losing bits.  (Note, this still won't be right
     * if the server supports flags and we were trying to *remove* flags
     * on a file that we copied, i.e., that we didn't create.)
     */
    errno = 0;
    if (fchflags(to_fd, sb.st_flags))
	if (errno != EOPNOTSUPP || sb.st_flags != 0)
	    warning("%s: set flags (was: 0%07o)", to, sb.st_flags);

    tval[0].tv_sec = sb.st_atime;
    tval[1].tv_sec = sb.st_mtime;
    tval[0].tv_usec = tval[1].tv_usec = 0;
    if (utimes(to, tval))
	warning("%s: set times", to);
    if (close(to_fd)) {
	warning("%s", to);
	return 1;
    }

    return 0;
}

void
movefile(char* fromfile, char* tofile, char* backupfile, int backup)
{
    if (backup) {
	if (fastcopy(fromfile, tofile))
	    error("fastcopy(%s, %s)", fromfile, tofile);
	if (rename(fromfile, backupfile))
	    error("rename(%s, %s)", fromfile, backupfile);
    } else {
	if (rename(fromfile, tofile)) {
	    if (errno != EXDEV)
		error("rename(%s, %s)", fromfile, tofile);
	    if (fastcopy(fromfile, tofile))
		error("fastcopy(%s, %s)", fromfile, tofile);
	    if (unlink(fromfile))
		error("unlink(%s)", fromfile);
	}
    }
}

void
process_maildir(int seq)
{
    struct stat sb;
    struct dirent* dp;
    DIR* dirp;
    char maildir[BUFSIZ];
    char backupdir[BUFSIZ];
    char mailfile[BUFSIZ];
    char inboxfile[BUFSIZ];
    char backupfile[BUFSIZ];
    char** list;
    int listsize = BUFSIZ;
    int listend = 0;
    int i;

    if ((list = malloc(sizeof(char*)*listsize)) == NULL)
	error("malloc");

    snprintf(maildir, sizeof(maildir), "%s/new", Mbox);
    snprintf(backupdir, sizeof(backupdir), "%s/cur", Mbox);
    if ((dirp = opendir(maildir)) == NULL)
	error("opendir(%s)", maildir);
    while ((dp = readdir(dirp)) != NULL) {
	snprintf(mailfile, sizeof(mailfile), "%s/%s", maildir, dp->d_name);
	if (lstat(mailfile, &sb))
	    continue;
	if (!((sb.st_mode & S_IFREG) && (sb.st_mode & S_IRUSR)))
	    continue;
	if (listend >= listsize) {
	    listsize *= 2;
	    if ((list = (char**)realloc(list, sizeof(char*)*listsize)) == NULL)
		error("realloc");
	}
	if ((list[listend++] = strdup(dp->d_name)) == NULL)
	    error("strdup(%s)", dp->d_name);
    }
    closedir(dirp);

    qsort(list, listend, sizeof(char*),
	  (int (*)(const void*, const void *))compare_string);

    for (i = 0; i < listend; i++) {
	do {
	    snprintf(inboxfile, sizeof(inboxfile), "%s/%d", InboxDir, ++seq);
	    if (access(inboxfile, R_OK) && errno == ENOENT)
		break;
	} while (TRUE);

	snprintf(mailfile, sizeof(mailfile), "%s/%s", maildir, list[i]);
	if (Backup)
	    snprintf(backupfile, sizeof(backupfile),
		     "%s/%s:2,S", backupdir, list[i]);
	movefile(mailfile, inboxfile, backupfile, Backup);
	printf("%d\n", seq);
    }
}

void
process()
{
    int seq = get_last_seq();

    switch (MboxType) {
    case T_MAILDIR:
	process_maildir(seq);
	break;
    }
}

void
sanity_check()
{
    /* XXX not yet */
}

int
main(int argc, char** argv)
{
    extern char* optarg;
    extern int optind;
    int ch;

    init_env(argc, argv);

    while ((ch = getopt(argc, argv, "hbd:i:")) != -1) {
	switch (ch) {
	case 'b':
	    Backup = TRUE;
	    break;
	case 'd':
	    snprintf(Mbox, sizeof(Mbox), "%s", optarg);
	    break;
	case 'i':
	    snprintf(InboxDir, sizeof(InboxDir), "%s", optarg);
	    break;
	case 'h':
	default:
	    usage();
	}
    }
    argc -= optind;
    argv += optind;

    sanity_check();
    process();
    return 0;
}


Mew-dist メーリングリストの案内