Imported from ../bash-2.05b.tar.gz.
[platform/upstream/bash.git] / mailcheck.c
1 /* mailcheck.c -- The check is in the mail... */
2
3 /* Copyright (C) 1987-2002 Free Software Foundation, Inc.
4
5 This file is part of GNU Bash, the Bourne Again SHell.
6
7 Bash is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Bash is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with Bash; see the file COPYING.  If not, write to the Free Software
19 Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include "bashtypes.h"
25 #include "posixstat.h"
26 #ifndef _MINIX
27 #  include <sys/param.h>
28 #endif
29 #if defined (HAVE_UNISTD_H)
30 #  include <unistd.h>
31 #endif
32 #include "posixtime.h"
33 #include "bashansi.h"
34
35 #include "shell.h"
36 #include "execute_cmd.h"
37 #include "mailcheck.h"
38 #include <tilde/tilde.h>
39
40 extern int mailstat __P((const char *, struct stat *));
41
42 typedef struct {
43   char *name;
44   char *msg;
45   time_t access_time;
46   time_t mod_time;
47   off_t file_size;
48 } FILEINFO;
49
50 /* The list of remembered mail files. */
51 static FILEINFO **mailfiles = (FILEINFO **)NULL;
52
53 /* Number of mail files that we have. */
54 static int mailfiles_count;
55
56 /* The last known time that mail was checked. */
57 static time_t last_time_mail_checked;
58
59 /* Non-zero means warn if a mail file has been read since last checked. */
60 int mail_warning;
61
62 static int find_mail_file __P((char *));
63 static void update_mail_file __P((int));
64 static int add_mail_file __P((char *, char *));
65
66 static int file_mod_date_changed __P((int));
67 static int file_access_date_changed __P((int));
68 static int file_has_grown __P((int));
69
70 static char *parse_mailpath_spec __P((char *));
71
72 /* Returns non-zero if it is time to check mail. */
73 int
74 time_to_check_mail ()
75 {
76   char *temp;
77   time_t now;
78   intmax_t seconds;
79
80   temp = get_string_value ("MAILCHECK");
81
82   /* Negative number, or non-numbers (such as empty string) cause no
83      checking to take place. */
84   if (temp == 0 || legal_number (temp, &seconds) == 0 || seconds < 0)
85     return (0);
86
87   now = NOW;
88   /* Time to check if MAILCHECK is explicitly set to zero, or if enough
89      time has passed since the last check. */
90   return (seconds == 0 || ((now - last_time_mail_checked) >= seconds));
91 }
92
93 /* Okay, we have checked the mail.  Perhaps I should make this function
94    go away. */
95 void
96 reset_mail_timer ()
97 {
98   last_time_mail_checked = NOW;
99 }
100
101 /* Locate a file in the list.  Return index of
102    entry, or -1 if not found. */
103 static int
104 find_mail_file (file)
105      char *file;
106 {
107   register int i;
108
109   for (i = 0; i < mailfiles_count; i++)
110     if (STREQ (mailfiles[i]->name, file))
111       return i;
112
113   return -1;
114 }
115
116 #define RESET_MAIL_FILE(i) \
117   do \
118     { \
119       mailfiles[i]->access_time = mailfiles[i]->mod_time = 0; \
120       mailfiles[i]->file_size = 0; \
121     } \
122   while (0)
123
124 static void
125 update_mail_file (i)
126      int i;
127 {
128   char *file;
129   struct stat finfo;
130
131   file = mailfiles[i]->name;
132   if (mailstat (file, &finfo) == 0)
133     {
134       mailfiles[i]->access_time = finfo.st_atime;
135       mailfiles[i]->mod_time = finfo.st_mtime;
136       mailfiles[i]->file_size = finfo.st_size;
137     }
138   else
139     RESET_MAIL_FILE (i);
140 }
141
142 /* Add this file to the list of remembered files and return its index
143    in the list of mail files. */
144 static int
145 add_mail_file (file, msg)
146      char *file, *msg;
147 {
148   struct stat finfo;
149   char *filename;
150   int i;
151
152   filename = full_pathname (file);
153   i = find_mail_file (filename);
154   if (i >= 0)
155     {
156       if (mailstat (filename, &finfo) == 0)
157         {
158           mailfiles[i]->mod_time = finfo.st_mtime;
159           mailfiles[i]->access_time = finfo.st_atime;
160           mailfiles[i]->file_size = finfo.st_size;
161         }
162       free (filename);
163       return i;
164     }
165
166   i = mailfiles_count++;
167   mailfiles = (FILEINFO **)xrealloc
168                 (mailfiles, mailfiles_count * sizeof (FILEINFO *));
169
170   mailfiles[i] = (FILEINFO *)xmalloc (sizeof (FILEINFO));
171   mailfiles[i]->name = filename;
172   mailfiles[i]->msg = msg ? savestring (msg) : (char *)NULL;
173   update_mail_file (i);
174   return i;
175 }
176
177 /* Reset the existing mail files access and modification times to zero. */
178 void
179 reset_mail_files ()
180 {
181   register int i;
182
183   for (i = 0; i < mailfiles_count; i++)
184     {
185       RESET_MAIL_FILE (i);
186     }
187 }
188
189 /* Free the information that we have about the remembered mail files. */
190 void
191 free_mail_files ()
192 {
193   register int i;
194
195   for (i = 0; i < mailfiles_count; i++)
196     {
197       free (mailfiles[i]->name);
198       FREE (mailfiles[i]->msg);
199       free (mailfiles[i]);
200     }
201
202   if (mailfiles)
203     free (mailfiles);
204
205   mailfiles_count = 0;
206   mailfiles = (FILEINFO **)NULL;
207 }
208
209 /* Return non-zero if FILE's mod date has changed and it has not been
210    accessed since modified. */
211 static int
212 file_mod_date_changed (i)
213      int i;
214 {
215   time_t mtime;
216   struct stat finfo;
217   char *file;
218
219   file = mailfiles[i]->name;
220   mtime = mailfiles[i]->mod_time;
221
222   if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0))
223     return (mtime != finfo.st_mtime);
224
225   return (0);
226 }
227
228 /* Return non-zero if FILE's access date has changed. */
229 static int
230 file_access_date_changed (i)
231      int i;
232 {
233   time_t atime;
234   struct stat finfo;
235   char *file;
236
237   file = mailfiles[i]->name;
238   atime = mailfiles[i]->access_time;
239
240   if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0))
241     return (atime != finfo.st_atime);
242
243   return (0);
244 }
245
246 /* Return non-zero if FILE's size has increased. */
247 static int
248 file_has_grown (i)
249      int i;
250 {
251   off_t size;
252   struct stat finfo;
253   char *file;
254
255   file = mailfiles[i]->name;
256   size = mailfiles[i]->file_size;
257
258   return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size));
259 }
260
261 /* Take an element from $MAILPATH and return the portion from
262    the first unquoted `?' or `%' to the end of the string.  This is the
263    message to be printed when the file contents change. */
264 static char *
265 parse_mailpath_spec (str)
266      char *str;
267 {
268   char *s;
269   int pass_next;
270
271   for (s = str, pass_next = 0; s && *s; s++)
272     {
273       if (pass_next)
274         {
275           pass_next = 0;
276           continue;
277         }
278       if (*s == '\\')
279         {
280           pass_next++;
281           continue;
282         }
283       if (*s == '?' || *s == '%')
284         return s;
285     }
286   return ((char *)NULL);
287 }
288
289 char *
290 make_default_mailpath ()
291 {
292   char *mp;
293
294   get_current_user_info ();
295   mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name));
296   strcpy (mp, DEFAULT_MAIL_DIRECTORY);
297   mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/';
298   strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name);
299   return (mp);
300 }
301
302 /* Remember the dates of the files specified by MAILPATH, or if there is
303    no MAILPATH, by the file specified in MAIL.  If neither exists, use a
304    default value, which we randomly concoct from using Unix. */
305 void
306 remember_mail_dates ()
307 {
308   char *mailpaths;
309   char *mailfile, *mp;
310   int i = 0;
311
312   mailpaths = get_string_value ("MAILPATH");
313
314   /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */
315   if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL")))
316     {
317       add_mail_file (mailpaths, (char *)NULL);
318       return;
319     }
320
321   if (mailpaths == 0)
322     {
323       mailpaths = make_default_mailpath ();
324       add_mail_file (mailpaths, (char *)NULL);
325       free (mailpaths);
326       return;
327     }
328
329   while (mailfile = extract_colon_unit (mailpaths, &i))
330     {
331       mp = parse_mailpath_spec (mailfile);
332       if (mp && *mp)
333         *mp++ = '\0';
334       add_mail_file (mailfile, mp);
335       free (mailfile);
336     }
337 }
338
339 /* check_mail () is useful for more than just checking mail.  Since it has
340    the paranoids dream ability of telling you when someone has read your
341    mail, it can just as easily be used to tell you when someones .profile
342    file has been read, thus letting one know when someone else has logged
343    in.  Pretty good, huh? */
344
345 /* Check for mail in some files.  If the modification date of any
346    of the files in MAILPATH has changed since we last did a
347    remember_mail_dates () then mention that the user has mail.
348    Special hack:  If the variable MAIL_WARNING is non-zero and the
349    mail file has been accessed since the last time we remembered, then
350    the message "The mail in <mailfile> has been read" is printed. */
351 void
352 check_mail ()
353 {
354   char *current_mail_file, *message;
355   int i, use_user_notification;
356   char *dollar_underscore, *temp;
357
358   dollar_underscore = get_string_value ("_");
359   if (dollar_underscore)
360     dollar_underscore = savestring (dollar_underscore);
361
362   for (i = 0; i < mailfiles_count; i++)
363     {
364       current_mail_file = mailfiles[i]->name;
365
366       if (*current_mail_file == '\0')
367         continue;
368
369       if (file_mod_date_changed (i))
370         {
371           int file_is_bigger;
372
373           use_user_notification = mailfiles[i]->msg != (char *)NULL;
374           message = mailfiles[i]->msg ? mailfiles[i]->msg : "You have mail in $_";
375
376           bind_variable ("_", current_mail_file);
377
378 #define atime mailfiles[i]->access_time
379 #define mtime mailfiles[i]->mod_time
380
381           /* Have to compute this before the call to update_mail_file, which
382              resets all the information. */
383           file_is_bigger = file_has_grown (i);
384
385           update_mail_file (i);
386
387           /* If the user has just run a program which manipulates the
388              mail file, then don't bother explaining that the mail
389              file has been manipulated.  Since some systems don't change
390              the access time to be equal to the modification time when
391              the mail in the file is manipulated, check the size also.  If
392              the file has not grown, continue. */
393           if ((atime >= mtime) || !file_is_bigger)
394             continue;
395
396           /* If the mod time is later than the access time and the file
397              has grown, note the fact that this is *new* mail. */
398           if (use_user_notification == 0 && (atime < mtime) && file_is_bigger)
399             message = "You have new mail in $_";
400 #undef atime
401 #undef mtime
402
403           if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES))
404             {
405               puts (temp);
406               free (temp);
407             }
408           else
409             putchar ('\n');
410         }
411
412       if (mail_warning && file_access_date_changed (i))
413         {
414           update_mail_file (i);
415           printf ("The mail in %s has been read\n", current_mail_file);
416         }
417     }
418
419   if (dollar_underscore)
420     {
421       bind_variable ("_", dollar_underscore);
422       free (dollar_underscore);
423     }
424   else
425     unbind_variable ("_");
426 }