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