[comp.mail.misc] Checking for new mail

paul@csnz.co.nz (Paul Gillingwater) (08/21/89)

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
would be helpful too.  I want a menu to display an indicator saying that
new mail has arrived when it comes into the user's mailbox.  It should
go away when they have read it.  I'd like it to look every so often,
maybe once per minute -- but it should check each time a new menu
is selected.

Any ideas?  Code fragments would be appreciated....

-- 
Paul Gillingwater, Computer Sciences of New Zealand Limited
Domain: paul@csnz.co.nz  Bang: uunet!vuwcomp!dsiramd!csnz!paul
Call Magic Tower BBS V21/23/22/22bis 24 hrs NZ+64 4 767 326
SpringBoard BBS for Greenies! V22/22bis/HST NZ+64 4 767 742

ken@capone.gatech.edu (Ken Seefried iii) (08/21/89)

---

Ummmmm...is there no 'biff' in System V (Xenix)?


	ken seefried iii	...!<anywhere>!gatech!ken
	ken@gatech.edu		

argv%eureka@Sun.COM (Dan Heller) (08/22/89)

In article <105@csnz.co.nz> paul@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@sun.com
 * Based on a great idea by Akkana <akkana@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@sun.com>
-----
My postings reflect my opinion only -- not the opinion of any company.

msir@uhura.cc.rochester.edu (Mark Sirota) (08/22/89)

In article <122772@sun.Eng.Sun.COM> argv@sun.UUCP (Dan Heller) writes:
>In article <105@csnz.co.nz> paul@csnz.co.nz (Paul Gillingwater) writes:
>> 	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....
>
> 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).

Similarly, I wrote a program which you call from your .forward file
(assuming you run sendmail), which informs you when new mail comes in.
This, I think, is better than a program which sits around and checks every
so often, because it tells you immediately, doesn't sit around taking CPU
time or memory or whatnot (which is a problem for those of us on machines
where CPU-time accounting is done), and unlike biff, it doesn't mess up your
screen when mail comes in (if you use your status line or titlebar).

My program isn't quite polished enough to publish, but perhaps it could be
if there was enough call for it.  Dan's program appears to be excellent; I
just wanted to recommend the concept of using your .forward file for this.
-- 
Mark Sirota - University of Rochester, Rochester, NY
 Internet: msir@cc.rochester.edu
 Bitnet:   msir@uordbv.bitnet
 UUCP:     {decvax,harvard,ames,rutgers}!rochester!ur-cc!msir

spcecdt@ucscb.UCSC.EDU (Space Cadet) (08/22/89)

In article <122772@sun.Eng.Sun.COM> argv@sun.UUCP (Dan Heller) writes:
>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).


      A friend of mine hacked sysline to run on his xenix box.  Works great.
Mail falcon@ucscb.ucsc.edu.

	John DuBois
	spcecdt@ucscb.ucsc.edu
	...!ucbvax!ucscc!ucscb!spcecdt

argv%eureka@Sun.COM (Dan Heller) (08/22/89)

In article <2847@ur-cc.UUCP> Mark Sirota <msir@cc.rochester.edu> writes:
> In article <122772@sun.Eng.Sun.COM> argv@sun.UUCP (Dan Heller) writes:
> >In article <105@csnz.co.nz> paul@csnz.co.nz (Paul Gillingwater) writes:
> >> 	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....
> >
> > So, I wrote a program called "watch" which watches your mailbox and,
> Similarly, I wrote a program which you call from your .forward file

The reason that I didn't choose to do it this way is because there are
some MTAs on smaller machines that don't recognize .forward.  watch is
more portable because it doesn't have this dependency.

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

davidsen@sungod.crd.ge.com (ody) (08/23/89)

  ksh does this for you. It's relatively easy to write your own in shell
if you want something fancy, check the mod time of the mailfile against
another file. You can use one message for mail since last reading, like
"you have mail" and one for mail since last informational message "you
have NEW mail." This can be hacked any way you want.

  The advantage of havinf it in shell is that it come out at the right
time, just before reading another command, and doesn't trash your
screen. Stuff on the status line is okay if you have a staus line.
	bill davidsen		(davidsen@crdos1.crd.GE.COM)
  {uunet | philabs}!crdgw1!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

jbayer@ispi.UUCP (Jonathan Bayer) (08/23/89)

davidsen@sungod.crd.ge.com (ody) writes:


>  ksh does this for you. It's relatively easy to write your own in shell

The standard /bin/sh also notifies you of new mail on SCO Xenix.

JB
-- 
Jonathan Bayer		Intelligent Software Products, Inc.
(201) 245-5922		500 Oakwood Ave.
jbayer@ispi.COM		Roselle Park, NJ   07204    

jonathan@cs.keele.ac.uk (Jonathan Knight) (08/25/89)

From article <1117@ispi.UUCP>, by jbayer@ispi.UUCP (Jonathan Bayer):

There are several techniques for finding out if new mail has
arrived.  Firstly, you can place a filter somewhere so that it
gets run whenever new mail arrives.  This method has been
mentioned earlier for use with sendmail.  All you have to do is
create a file called .forward with the line like this:
	"| /fs/cs/jonathan/inboundmail"
and then write a short shell script called
/fs/cs/jonathan/inboundmail like this:
	#! /bin/sh
	/bin/mail -d jonathan
	echo "New mail has just been delivered" > /dev/ttyk5

This will have the desired effect if you were me and you always
used /dev/ttyk5 to log in, anyway, the idea is there.

Another method is to check the users mailbox periodically to see
if the access times have changed.  This is used by the shell's
that say "You have new mail".  All they do is remember when the
last modification time was, and then check it every now and again
until it changes.  Various programs will run in the background and
check this every minute, "newmail" which is part of the elm system
springs to mind.

A third method is to deduce when new mail has arrived by looking
at the access and modification times of the mailbox.  I have
written a program called cmail which will report whether new mail
has arrived in a users mailbox.  From studying the effects of
elm, mail, mailx, sendmail and /bin/mail I developed an algorithm
to do this.  I have posted it to comp.sources.unix some weeks ago
so I suppose it will appear soon.

Does this answer the original question?
-- 
  ______    JANET :jonathan@uk.ac.keele.cs     Jonathan Knight,
    /       BITNET:jonathan%cs.kl.ac.uk@ukacrl Department of Computer Science
   / _   __ other :jonathan@cs.keele.ac.uk     University of Keele, Keele,
(_/ (_) / / UUCP  :...!ukc!kl-cs!jonathan      Staffordshire.  ST5 5BG.  U.K.

bill@twwells.com (T. William Wells) (08/27/89)

In article <105@csnz.co.nz> paul@csnz.co.nz (Paul Gillingwater) writes:
:         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
: would be helpful too.  I want a menu to display an indicator saying that
: new mail has arrived when it comes into the user's mailbox.  It should
: go away when they have read it.  I'd like it to look every so often,
: maybe once per minute -- but it should check each time a new menu
: is selected.

I saw a lot of answers, none of which seemed to answer the question.
If I understand you, you want your C program to check to see if mail
has arrived in the user's mailbox.

On many (all?) Unix systems, there is a shell variable MAIL that
contains the name of the user's mailbox. If yours doesn't provide
one, you can generally set up such a variable in the /etc/profile or
whatever your shell uses for initialization at login.

Anyway, given the variable, you then use the stat() system call to
see what the file modification time is. If it is later than the file
modification time since you last checked, new mail has arrived.
(Barring other things that might modify this file, like reading the
mail.)

Here is a sample function to do this; this was just typed in by hand:
caveat usor!

#include <sys/types.h>
#include <sys/stat.h>

/* There are two functions done here, the init function and the check
   function. Normal use is as follows:

   has_mail = mail_check(1);

   is called at the start of the program and after the user has read
   his mail or told the program that he doesn't care to know about
   mail that is already in the box. It returns true if the mailbox
   exists.

   has_mail = mail_check(0);

   is called periodically, to see if mail has arrived since the last
   initialization. It returns true when mail has arrived, and
   continues to return true till the function is called with init=1
   (Barring wierdness like touching the mail file).

   Note that this function insists on using the mail file specified
   at the first call; changing the MAIL variable afterwords will have
   no effect. */

int
mail_check(init)
int     init;
{
	struct stat statbuf;

	static char *mail_file;
	static time_t last_time;

	/* Get the name of the mail file. If there is none, assume
	   that the user doesn't want a mail check. */

	if (!mail_file) {
		mail_file = getenv("MAIL");
	}
	if (!mail_file) {
		return (0);
	}
	/* Get the file modification time. If there isn't a file (or
	   the file is otherwise unaccessible), pretend that it was
	   modified at the earliest possible moment. */

	if (stat(mail_file, &statbuf) < 0) {
		statbuf.st_mtime = 0;
	}
	/* If initializing, just squirrel away the time; return true
	   if the file exists. */

	if (init) {
		last_time = statbuf.st_mtime;
		return (last_time != 0);
	}
	/* Return true if the current modification time is greater
	   than the saved modification time. */

	return (statbuf.st_mtime > last_time);
}

---
Bill                    { uunet | novavax | ankh | sunvice } !twwells!bill
bill@twwells.com

vjs@calcite.UUCP (Vernon Schryver) (08/28/89)

There is a trouble with schemes for comparing mtime & atime to see if new
mail has arrived.

Atime>=mtime after /bin/mail has delivered mail, unless it cannot read the
mail box.  Thus, since /bin/mail is SGID=mail to write it, you have to make
/usr/mail/$USER unreadable by group.  Note that breaks /bin/mail's
forwarding mechanism.

If you use the bit of dung called mailx to look at mail, and delete some
but not all of the contents of your mailbox, mailx will carefully restore
atime<mtime.  The stupidity is reasonably obvious in the source.

These bugs are in SVR3.[012].  Perhaps they are absent in Xenix.

Vernon Schryver
vjs@calcite.uucp    or    {pyramid,sgi}!calcite!vjs

bmw@isgtec.UUCP (Bruce Walker) (08/29/89)

Since I haven't seen anyone mention it yet, I will:
in the extremely well-written and useful book "The UNIX Programming
Environment" by Kernighan and Pike, there is a small bit of C called
"checkmail" (pg. 216).  To link it you'll also need a code fragment
called "error()" (pg. 207).

This program does what none of the to-date posted ones do: it checks
for the mail file *growing*, not just *changing*; otherwise you'll
get "you have mail" messages after you read your mail (and it is
deleted from your mailbox).

Not mentioned in the book though, is the fact that this program will
not work as advertised on BSD systems.  You are supposed to start it
like this (usually from .login):

	$ checkmail&

which is fine under SysX, since when you log out, it goes away by
itself.  However, if you do this on a Sun (f'rinstance), the process
disconnects from your tty and continues running (inherited by root).
You end up with dozens of the little buggers.

My (slightly klugey) solution is to add a line to my .logout (csh):

	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&

Anyone know a better way?
-- 
Bruce Walker                          ...uunet!mnetor!lsuc!isgtec!bmw
"Better Living Through Connectivity"         ...utzoo!lsuc!isgtec!bmw
ISG Technologies Inc. 3030 Orlando Dr. Mississauga. Ont. Can. L4V 1S8

seth@ctr.columbia.edu (Seth Robertson) (08/30/89)

In article <128@isgtec.UUCP> bmw@isgtec.UUCP (Bruce Walker) writes:
>the process
>disconnects from your tty and continues running ...

>My (slightly klugey) solution is to add a line to my .logout (csh):

>	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&

>Anyone know a better way?


Sure,  Modify checkmail so that does the following:

	if (kill(ppid,0))
	  exit(1);

So every so often, it checks to see if its parent is around.  If
it isn't, it performs honorable suicide.

Actually, it is also a good idea for the program to make its real PPID
to be 1.  That way you won't accidently kill it && you won't see it
when you do things like `jobs` (You would do it by having the program
call itself && having the first copy kill itself off.  The second copy
then gets a new process group and its parent id gets reset to 1.)

-- 
                                        -Seth Robertson
                                         seth@ctr.columbia.edu

shap@bunker.UUCP (Joseph D. Shapiro) (08/30/89)

In article <128@isgtec.UUCP> bmw@isgtec.UUCP (Bruce Walker) writes:
>My (slightly klugey) solution is to add a line to my .logout (csh):
>
>	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&
>
>Anyone know a better way?

when initiating it in .login:

	checkmail&
	echo $child > ~/kill.$$

in .logout:

	kill -9 ` cat ~/kill.$$ `
-- 
__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__
Joe Shapiro					"My other car is a turbo...
ISC-Bunker Ramo     				 ...too."
{decvax,yale,philabs,oliveb}!bunker!shap

amos@taux01.UUCP (Amos Shapir) (08/30/89)

In article <128@isgtec.UUCP> bmw@isgtec.UUCP (Bruce Walker) writes:
>You end up with dozens of the little buggers.
>
>My (slightly klugey) solution is to add a line to my .logout (csh):
>
>	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&
>
>Anyone know a better way?

alias logout 'kill -1 0'

This works in most versions of csh.

-- 
	Amos Shapir		amos@taux01.nsc.com or amos@nsc.nsc.com
National Semiconductor (Israel) P.O.B. 3007, Herzlia 46104, Israel
Tel. +972 52 522261  TWX: 33691, fax: +972-52-558322
34 48 E / 32 10 N			(My other cpu is a NS32532)

jdarcy@multimax.UUCP (Jeff d'Arcy,OS Avenue,2627,) (08/30/89)

bmw@isgtec.UUCP (Bruce Walker):
> [description of mail check program not terminating on BSD ]
>
> You end up with dozens of the little buggers.
> 
> My (slightly klugey) solution is to add a line to my .logout (csh):
> 
> 	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&

What a happy coincidence!  After changing jobs and finding no such beast
available, I wrote a program that does just what you describe.  I solved
the exact same problem by adding code to the main loop that checks the
parent process ID against whatever it was when the program started.  If
the two don't match, the parent obviously went away.  In two weeks of 
constant use on several machines (UmaxV and Umax4.x) I have seen neither
premature terminations or excessive persistence.

Jeff d'Arcy		jdarcy@encore.com		(508) 460-0500 x2627
  I am solely responsible for my own opinions, and not at all for others'

jdarcy@multimax.UUCP (Jeff d'Arcy,OS Avenue,2627,) (08/30/89)

seth@ctr.columbia.edu (Seth Robertson):
> Sure,  Modify checkmail so that does the following:
> 
> 	if (kill(ppid,0))
> 	  exit(1);
> 
> So every so often, it checks to see if its parent is around.  If
> it isn't, it performs honorable suicide.

This is an approach I'd tried earlier (I used SIGCHLD, actually) and
it worked OK, but I wasn't comfortable with the possibility that the
parent might try to do something with the signal instead of dropping
it.  That's when I thought of comparing initial vs. current PPIDs.

Jeff d'Arcy		jdarcy@encore.com		(508) 460-0500 x2627
  I am solely responsible for my own opinions, and not at all for others'

morrison@ficc.uu.net (Brad Morrison) (08/31/89)

In article <128@isgtec.UUCP>, bmw@isgtec.UUCP (Bruce Walker) writes:
> You are supposed to start it like this (usually from .login):

> 	$ checkmail&

> My (slightly klugey) solution is to add a line to my .logout (csh):

> 	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&

> Anyone know a better way?

At the beginning of your program, use signal() (Read The Fine Manual)
to trap SIGHUP.

Also, you could fork() before the main control loop, so the program will
go into the background by itself.

Newsgroup comp.unix.wizards removed from distribution.
-- 
Brad Morrison                (713) 274-5449 | "If it doesn't come from you,
Ferranti International Controls Corporation |  shouldn't it come from Gerber?"
uunet!ficc!morrison    morrison@ficc.uu.net |  -- Bristol Meyers baby formula ad

gregg@cbnewsc.ATT.COM (gregg.g.wonderly) (09/01/89)

In article <128@isgtec.UUCP>, bmw@isgtec.UUCP (Bruce Walker) writes:
> You are supposed to start it like this (usually from .login):
 
> 	$ checkmail&
 
> My (slightly klugey) solution is to add a line to my .logout (csh):
 
> 	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&
 
> Anyone know a better way?

Yes, just do

	sh -c 'checkmail &'

This will start the process as a subprocess of sh(1) with the process
group left intact.  Last I knew, the bourne shell had not been infected
with this bug.  I say bug because it really is.  The process should get
SIGHUP unless I 'nohup' it myself.

-- 
-----
gregg.g.wonderly@att.com   (AT&T bell laboratories)

guy@auspex.auspex.com (Guy Harris) (09/01/89)

>This is an approach I'd tried earlier (I used SIGCHLD, actually) and
>it worked OK, but I wasn't comfortable with the possibility that the
>parent might try to do something with the signal instead of dropping
>it.

To quote from the 4.3-tahoe "kill" man page:

	"Kill" sends the signal "sig" to a process, specified by the
	process number "pid".  "Sig" may be one of the signals specified in
	"sigvec(2)", or it may be 0, in which case error checking is
	performed but no signal is actually sent.  This can be used to
	check the validity of "pid".

and from the SVID:

	The signal that is to be sent is specified by the argument "sig"
	and is either one from the list given in SIGNAL(BA_OS) or 0.  If
	"sig" is 0 (the null signal), error checking is performed but
	no signal is actually sent.  This can be used to check the
	validity of "pid".

In 4.[23]BSD and System V, at least, your fears are groundless; the code
does what the documentation claims it does - the parent doesn't even
*see* "signal 0".

corbet@mead.uucp (Jonathan Corbet) (09/02/89)

bmw@isgtec.UUCP (Bruce Walker):

>However, if you do this on a Sun (f'rinstance), the process
>disconnects from your tty and continues running (inherited by root).
>You end up with dozens of the little buggers.
>
>My (slightly klugey) solution is to add a line to my .logout (csh):
>
>	/bin/kill -9 `ps x | awk '$5=="checkmail" {print $1}'`&
>
>Anyone know a better way?

How about if you add a line like:

	signal (SIGHUP, SIG_DFL);

toward the beginning of your main program?  That will cause the program
to die when the parent process does...


Jonathan Corbet
National Center for Atmospheric Research, Field Observing Facility
corbet@stout.ucar.edu

cudcv@warwick.ac.uk (Rob McMahon) (09/02/89)

In article <4201@ncar.ucar.edu> corbet@mead.UCAR.EDU (Jonathan Corbet) writes:
>How about if you add a line like:
>
>	signal (SIGHUP, SIG_DFL);
>
>toward the beginning of your main program?  That will cause the program
>to die when the parent process does...

I've seen a couple of people say things like this now.  It's not that csh sets
its children to ignore SIGHUP, but rather that they never see it.  Csh, at
least on BSD systems with job control, puts each `job' into a process group of
it's own, so that it can manipulate them (stop them as a complete job, move
them into the foreground by setting the process group associated with the
terminal the same as the job, put them into the background by doing the
opposite).

Since only processes in the process group associated with the terminal see
keyboard generated signals, including SIGHUP, background processes never even
have the signals delivered to them.  So the only ways round this are to either
have the shell kill the job before it goes away (by putting something in
.logout), or by having the job check that its parent is still around
occasionally, by checking the value of ppid(), or by kill(orig_ppid, 0)'ing.

Rob
-- 
UUCP:   ...!mcvax!ukc!warwick!cudcv	PHONE:  +44 203 523037
JANET:  cudcv@uk.ac.warwick             ARPA:   cudcv@warwick.ac.uk
Rob McMahon, Computing Services, Warwick University, Coventry CV4 7AL, England