Checking for new mail

Dan Heller argv%eureka at Sun.COM
Tue Aug 22 03:00:32 AEST 1989


In article <105 at csnz.co.nz> paul at csnz.co.nz (Paul Gillingwater) writes:
> Hi,
> 	What's the best way to check for the arrival of new mail?
> I'm writing in C using SCO Xenix 2.3.1, but no doubt other approaches
> Any ideas?  Code fragments would be appreciated....

Someone asked if you had biff, and I don't think you do.  Even if you
did, biff's biggest problem is that it ruin's your whole screen when
new mail comes in and if you're in an editor, the newlines don't work
right and it's just plain ugly.  Sysline is probably the nicest utility
for this type of thing because it writes to your status line (most smart
terminals have a status line these days).  However, sysline is not port-
able to system V or xenix (since last I checked).  (btw, does anyone
know where that came from?  I never saw it posted.)

So, I wrote a program called "watch" which watches your mailbox and,
in the event of new mail, it prints info about the mail (who it's from,
when it arrived, and the subject) on the terminal's status line.  For
window systems, it writes it to the titlebar of your window (command
line specification for this may be necessary depending on your window).
There are many options which include printing the time and/or your
current working directory, or anything you want on the status line.

The last time I posted this (as part of the Dclock widget submission
in comp.sources.x), it was not portable (strictly BSD).  But, I just
hacked it up to be sys-v/xenix compatible.  See leading comments at
the top of the file.  I encourage people to make fixes and send me patches.

/*
 * watch -- Simple program which watches for new mail (replaces obnoxious biff)
 * Ring bell and prints on status line (stdout if no status line) the
 * author of the mail and the subject ( "(No Subject)" if none).
 *
 * Author: Dan Heller island!argv at sun.com
 * Based on a great idea by Akkana <akkana at net1.ucsd.edu>
 *
 * Automatcially exits when user logs out.
 *
 * The program runs in background by default.  On startup, the process id of
 * the child is output to stdout.
 *
 * Options:
 *
 * -w for sunwindows (because you don't own your tty). This option need
 *    not be given if you are running suntools. If you are rlogged into another
 *    machine from a shelltool, then you will need to specify -w.
 *
 * -t displays the time whenever the status line is updated. If the timeout
 *    between updates is one minute, then the time displayed will be accurate.
 *
 * -T timeout   A timeout must be given -- this timeout is the number of
 *    seconds to wait between updates.  30 is the minimum timeout.
 *
 * -f filename  A filename must be specified. This file is read and its
 *    contents (one line) displayed on the status line (after the time, if
 *    specified).  The purpose for this flag is to allow the user to have
 *    his current working directory in the file.  Whenever the user changes
 *    directories, the current dir is put into the file, and the watch
 *    program is sent a SIGALRM and the status line is updated.  The alias
 *    and related commands to accomplish this:
 *
 *    % set watch = `watch`
 *    % alias cd 'cd \!*; pwd >! ~/file; kill -ALRM $watch'
 *
 *    The pid of the watch program is output and subsequently set in the
 *    "watch" shell variable.  You can send signals to the program by
 *    referencing the $watch variable as the process id of the program.
 *
 * -ts "string"
 * -fs "string"
 *    The strings are the escape sequences to go "to statusline" (-ts)
 *    and "from statusline" respectively.  This is provided for terminals
 *    whose termcap entries don't have this information even tho it exists
 *    or if you want to use an alternate status line (e.g. the top line of
 *    your terminal).  For example, the Wyse50's escape sequence to use the
 *    top statusline is ESC-F (^[F) -- to return would be just a carriage
 *    return (^M).  If -ts is given and -fs is missing, -fs defaults to ^M.
 *
 * -m file
 *    Uses "file" as mailfile rather than the defaut: /usr/spool/mail or
 *    whatever MAILDIR is defined to be above.
 *
 * -d Turns on debugging.  The program is not run in background.
 *
 * Hasn't been ported to sys-v or xenix, but the framework has been laid
 * out.  To get started, #define SYSV ...
 * There may be some include file problems, and you're going to have a problem
 * with setitimer().  The current hack to get this to work for systems without
 * setitimer is to use sleep(2) instead.  But this is untested -- if it doesn't
 * work, after the call to sleep(interval), call alrm().
 *
 * Bugs:
 *   If terminal has no status line capability, it prints to stdout which
 * can be annoying, but that's the user's fault for running it.
 *   The mail format requires the "From " line format to separate messages.
 * MMDF users can't use this program without hacking it.
 *
 * compile: cc -O -s watch.c -ltermlib -o watch
 */

#define MAILDIR "/usr/spool/mail"
#define UTMP	"/etc/utmp"
#ifdef SYSV
#define rindex strrchr
#define index strchr
#else
#define SYS_ERRLIST
#endif

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <pwd.h>
#include <sys/time.h>
#include <ctype.h>
#include <strings.h>
#include <utmp.h>

#ifdef sun
#include <sundev/kbd.h>
#include <sundev/kbio.h>
extern char *sprintf();
#endif /* sun */

#ifndef MAXPATHLEN
#define MAXPATHLEN BUFSIZ
#endif /* MAXPATHLEN */

int debug, COLS = 80, iswindow, beeper, alrm(), print_time, hs;
long last_size, last_access;

char *from(), *Time(), *print_dir, *dir();
char spoolfile[MAXPATHLEN], bp[1024], ts[16], fs[16];
struct stat stbuf;

#ifdef SYS_ERRLIST
extern int errno;
extern char *sys_errlist[];
#endif /* SYS_ERRLIST */

FILE *Tty;

char *usage = "usage: %s [-d] [-w] [-t] [-T timeout] [-f file] [-m file] [-ts \"string\"] [-fs \"string\"]\n";

main(argc, argv)
char **argv;
{
    char *getenv(), *getname(), *ttyname(), *rindex(), *cmd, *tty;
    struct itimerval timer;
    int interval = 60, pid;

    if (cmd = rindex(*argv, '/'))
	cmd++;
    else
	cmd = *argv;

#ifdef sun
    if (getenv("WINDOW_PARENT"))
	iswindow = 1;
#endif /* sun */

    while (*++argv)
	if (!strcmp(*argv, "-d"))
	    debug = 1;
	else if (!strcmp(*argv, "-T")) {
	    if (!*++argv) {
		printf("%s: -T requires time argument of >= 30 seconds.\n",cmd);
		exit(1);
	    }
	    if ((interval = atoi(*argv)) < 30 && !debug)
		printf("Minimum time interval is %d seconds.\n", interval = 30);
	} else if (!strcmp(*argv, "-w"))
	    iswindow = 1;
	else if (!strcmp(*argv, "-t"))
	    print_time = 1;
	else if (!strcmp(*argv, "-f"))
	    if (!*++argv)
		printf("%s: -f requires filename argument.\n", cmd), exit(1);
	    else
		print_dir = *argv;
	else if (!strcmp(*argv, "-m"))
	    if (!*++argv)
		printf("%s: -m requires filename argument.\n", cmd), exit(1);
	    else
		(void) strcpy(spoolfile, *argv);
	else if (!strcmp(*argv, "-ts")) {
	    if (!*++argv) {
		printf("%s: -ts requires 'to statusline' sequence.\n", cmd);
		exit(1);
	    }
	    (void) strcpy(ts, *argv);
	    (void) strcpy(fs, "\012");
	} else if (!strcmp(*argv, "-fs")) {
	    if (!*++argv) {
		printf("%s: -ts requires 'from statusline' sequence.\n", cmd);
		exit(1);
	    }
	    (void) strcpy(fs, *argv);
	} else
	    fprintf(stderr, usage, cmd), exit(1);

    if (fs[0] && !ts[0])
	printf("%s: -fs requires the -ts option be specified as well.\n", cmd);

    init_cadr(ts[0] != 0);
    beeper = beep();

    if (spoolfile[0] == 0)
	(void) sprintf (spoolfile, "%s/%s", MAILDIR, getname());

    /* get the tty name */
    if (isatty(fileno(stderr)))
	tty = ttyname(fileno(stderr));
    else if (isatty(fileno(stdout)))
	tty = ttyname(fileno(stdout));
    else if (isatty(fileno(stdin)))
	tty = ttyname(fileno(stdin));
    else
	fprintf(stderr, "You don't have to redirect *everything*!\n"), exit(1);

    if (!debug && (pid = fork())) {	/* Parent goes into the background */
	printf("%d\n", pid);  /* print the pid of the child */
	fflush(stdout);
	exit(0);
    }
    (void) signal(SIGINT, SIG_IGN);
    (void) signal(SIGQUIT, SIG_IGN);
#ifdef SIGTSTP
    (void) signal(SIGTSTP, SIG_IGN);
    (void) signal(SIGCONT, SIG_IGN);
#endif /* SIGTSTP */
    (void) close(fileno(stdout));
    (void) close(fileno(stderr));
    sleep(1);	/* sleep to give parent a chance to print pid */

    if (!stat(spoolfile, &stbuf)) {
	last_size = stbuf.st_size;
	last_access = stbuf.st_atime;
    }

    (void) signal(SIGALRM, alrm);

#ifdef ITIMER_REAL
    timer.it_value.tv_sec = interval;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = interval;
    timer.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &timer, 0);
#endif /* ITIMER_REAL */

    if (!(Tty = fopen(tty, "w")))
	exit(1);
    tty += 5; /* get rid of "/dev/" */
    alrm(); /* initialize */
    while (still_logged_in(tty)) {
#ifdef ITIMER_REAL
	pause();
#else /* ITIMER_REAL */
	sleep(interval);
	/* alrm(); */
#endif /* ITIMER_REAL */
    }
    fprintf(Tty, "Exiting at %s\n", Time());
}

alrm()
{
    char buf[256], mail_msg[256];
    register char *dir_p = "", *time_p = "";
    char *fromp;
    static unread;
    int do_print = 0;

    /* if there's mail, set do_print to guarantee dir() to give the right
     * information. read mail into tmp buffer.
     */
    *mail_msg = 0;
    if (!stat(spoolfile, &stbuf)) {
	if (stbuf.st_size > last_size && *(fromp = from(last_size))) {
	    do_print = 1;
	    bell(&beeper);
	    (void) sprintf(mail_msg, "New mail %s", fromp);
	    unread = 2;
	} else if (stbuf.st_atime > last_access) {
	    last_access = stbuf.st_atime;
	    if (hs && unread)
		do_print = 1;
	    unread = 0;
	} else if (unread) {
	    if (unread > 1 && hs)
		unread = do_print = 1;
	    (void) strcpy(mail_msg, "(mail)");
	}
	last_size = stbuf.st_size;
    }

    if (print_time)
	time_p = Time(), do_print = 1;

    if (print_dir)
	dir_p = dir(&do_print);

    if (do_print) {
	(void) sprintf(buf, "%s%s %s", time_p, dir_p, mail_msg);
	fprintf(Tty, "%s%.*s%s", ts, COLS-2 - strlen(ts) - strlen(fs), buf, fs);
	fflush(Tty);
    }
}

char *
dir(force)
int *force;
{
    FILE *fp;
    static char buf[256], buf2[256];
    char *p;
    extern char *index();

    (void) strcpy(buf2, buf);
    if (!(fp = fopen(print_dir, "r")))
	return NULL;
    p = fgets(buf, sizeof buf, fp);
    fclose(fp);
    if (!p || !*force && !strcmp(buf, buf2))
	return NULL;
    *force = 1;
    if (p = index(buf, '\n'))
	*p = 0;
    return buf;
}

bell(ding)
register int *ding;
{
#ifdef KIOCCMD
    if (*ding != -1) {
  	int cmd = KBD_CMD_BELL;
        struct timeval timer;
        timer.tv_sec = 0;
        timer.tv_usec = 100000;
        if (ioctl(*ding, KIOCCMD, &cmd))
	    *ding = -1;
        select(32, 0,0,0, &timer);
        cmd = KBD_CMD_NOBELL;
        if (ioctl(*ding, KIOCCMD, &cmd))
	    *ding = -1;
    } else
#endif /* KIOCCMD */
	putchar(7), fflush(Tty);
}

/* return username: username doesn't have to be declared static cuz funcs
 * that get the username points to static data (bug, but I know what I'm
 * doing).
 */
char *
getname()
{
    char *username, *getlogin();

    if (!(username = getlogin())) {
    	struct passwd *getpwuid(), *pwent;
    	if (!(pwent = getpwuid(getuid()))) {
	    perror("getpwuid");
	    fprintf(stderr, "I don't know you.  Try using -m <file>\n");
	    exit(0);
	}
	username = pwent->pw_name;
	endpwent(); /* close the passwd file */
    }
    return username;
}

/* Explanation of this silly routine:
 * return the fd of /dev/kbd if we're not rlogged in or from a pseudo-tty.
 * if return -1, bell() will just putchar(7), else it'll do an ioctl
 * to /dev/kbd to ring the bell on fd "kbd".  Note, this is sun-specific
 * cuz there are problems with "ringing the bell" on certain shell-like
 * applications on the sun...
 */
beep()
{
    int kbd = -1;
#ifdef sun
    char *getenv(), *p = getenv("TERM");
    if (!iswindow && p && !strcmp(p, "sun")) {
	kbd = open("/dev/kbd", O_WRONLY, 0);
	if (debug)
	    fprintf(Tty, "%d\n", kbd);
    }
#endif /* sun */
    return kbd;
}

init_cadr(ts_known)
{
    char *getenv(), *tgetstr(), *termname;
    char *tmp[1];

    if (ts_known) {
	termname = getenv("TERM");
	COLS = 80;
	return;
    }

    if (!(termname = getenv("TERM"))) {
       puts("No terminal type!");
       goto no_type;
    }
    if (tgetent(bp, termname) <= 0) {
	perror(termname);
	COLS = 80;
    } else
	COLS = tgetnum("co");

    if (debug)
	fprintf(Tty, "Terminal type = \"%s\"\n", termname);
    if (hs = tgetflag("hs")) {
        tmp[0] = ts;
        tgetstr("ts", tmp);
        tmp[0] = fs;
        tgetstr("fs", tmp);
#ifdef sun
    } else if (!strncmp(termname, "sun", 3) || iswindow) {
	struct ttysize win;
	if (!ioctl(0, TIOCGSIZE, &win))
	    COLS = win.ts_cols;
	/* if iswindow is false, user is not running suntools */
	if (iswindow || !strncmp(ttyname(0), "/dev/ttyp", 9)) {
	    iswindow = hs = 1;
	    (void) strcpy(ts, "\033]l");
	    (void) strcpy(fs, "\033\\");
	}
#endif /* sun */
    } else if (!strncmp(termname, "wy", 2)) { /* bad hack for wyse's */
	(void) strcpy(ts, "\033f");
	(void) strcpy(fs, "\r");
	hs = 1;
    } else {
no_type:
	puts("No status line capability -- output to stdout");
	(void) strcpy(ts, " ");
	(void) strcpy(fs, "\r\n");
    }
    if (debug)
	bell(&beeper), fprintf(Tty, "%sThis is a test!%s", ts, fs);
}

char *
Time()
{
    static char time_buf[11];
    struct tm *T;
    long x;

    time(&x), T = localtime(&x);
    (void) sprintf(time_buf, "%d:%02d ",
	(T->tm_hour) ? ((T->tm_hour <= 12) ? T->tm_hour : T->tm_hour - 12) : 12,
	T->tm_min);
    return time_buf;
}

char *
from(offset)
register long offset;
{
    static char buf[256];
    char buf2[128];
    FILE *fp;
    register char *p;
    int nomatch = 1;

    (void) strcpy(buf, "From \"unknown\"");
    if (!(fp = fopen(spoolfile, "r")) || fseek(fp, offset, 0L)) {
#ifdef SYS_ERRLIST
	(void) strcpy(buf, sys_errlist[errno]);
#else
	perror(spoolfile);
#endif /* SYS_ERRLIST */
	goto the_end;
    }
    while (fgets(buf, sizeof(buf), fp) && (nomatch = strncmp(buf, "From ", 5)))
	;
    if (nomatch) {
	buf[0] = 0;
	goto the_end;
    }

    if (p = index(buf+5, ' '))
	*p = 0;
    else
	goto the_end;
    p += strlen(strcpy(p, " -> "));

    while (fgets(buf2, sizeof(buf2), fp))
	if (*buf2 == '\n') {
	    (void) sprintf(p, " (No Subject)");
	    break;
	} else if (sscanf(buf2, "Subject: %[^\n]s", p))
	    break;
the_end:
    fclose(fp);

    /* Reset the access time of the spool file to prevent
     * bogus "new mail" messages from the shell.
     */
    {
#ifdef ITIMER_REAL
	struct timeval times[2];
	times[0].tv_sec = stbuf.st_atime;
	times[0].tv_usec = 0;
	times[1].tv_sec = stbuf.st_mtime;
	times[1].tv_usec = 0;
	if (!strncmp(MAILDIR, spoolfile, strlen(MAILDIR)) &&
	    utimes(spoolfile, times))
	    perror("utime");
#else
	long times[2];
	times[0] = stbuf.st_atime;
	times[1] = stbuf.st_mtime;
	if (!strncmp(MAILDIR, spoolfile, strlen(MAILDIR)) &&
	    utime(mailfile, times))
	    perror("utime");
#endif /* ITIMER_REAL */
    }
    return buf;
}

/*
 * return 1 if user is still logged in this tty.  0 otherwise.
 * It could be that the user logs out and then back in on the
 * same tty before this gets called again (especially if you're
 * running in a window-based environment like suns or X windows).
 */
still_logged_in(tty)
char *tty;
{
    struct utmp buf;
    static int fd;
    static long pos; /* once our position in utmp is found, always seek there */

    if (!fd && (fd = open(UTMP, 0)) == -1)
	return 0;

    if (lseek(fd, pos, 0) == -1)
	return 0;
    while (read(fd, (char *) &buf, sizeof buf) == sizeof buf)
	if (buf.ut_line[0] && !strcmp(buf.ut_line, tty))
	    break;
	else {
	    if (debug && buf.ut_line[0])
		fprintf(Tty, "%s <-> %s\n", buf.ut_line, tty);
	    pos += sizeof buf;
	}
    /* it's impossible to exit this loop without making a match */
    return buf.ut_name[0] != 0;
}

dan <island!argv at sun.com>
-----
My postings reflect my opinion only -- not the opinion of any company.



More information about the Comp.unix.xenix mailing list