Imported from ../bash-3.0.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 #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 static void
126 update_mail_file (i)
127      int i;
128 {
129   char *file;
130   struct stat finfo;
131
132   file = mailfiles[i]->name;
133   if (mailstat (file, &finfo) == 0)
134     {
135       mailfiles[i]->access_time = finfo.st_atime;
136       mailfiles[i]->mod_time = finfo.st_mtime;
137       mailfiles[i]->file_size = finfo.st_size;
138     }
139   else
140     RESET_MAIL_FILE (i);
141 }
142
143 /* Add this file to the list of remembered files and return its index
144    in the list of mail files. */
145 static int
146 add_mail_file (file, msg)
147      char *file, *msg;
148 {
149   struct stat finfo;
150   char *filename;
151   int i;
152
153   filename = full_pathname (file);
154   i = find_mail_file (filename);
155   if (i >= 0)
156     {
157       if (mailstat (filename, &finfo) == 0)
158         {
159           mailfiles[i]->mod_time = finfo.st_mtime;
160           mailfiles[i]->access_time = finfo.st_atime;
161           mailfiles[i]->file_size = finfo.st_size;
162         }
163       free (filename);
164       return i;
165     }
166
167   i = mailfiles_count++;
168   mailfiles = (FILEINFO **)xrealloc
169                 (mailfiles, mailfiles_count * sizeof (FILEINFO *));
170
171   mailfiles[i] = (FILEINFO *)xmalloc (sizeof (FILEINFO));
172   mailfiles[i]->name = filename;
173   mailfiles[i]->msg = msg ? savestring (msg) : (char *)NULL;
174   update_mail_file (i);
175   return i;
176 }
177
178 /* Reset the existing mail files access and modification times to zero. */
179 void
180 reset_mail_files ()
181 {
182   register int i;
183
184   for (i = 0; i < mailfiles_count; i++)
185     {
186       RESET_MAIL_FILE (i);
187     }
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. */
212 static int
213 file_mod_date_changed (i)
214      int i;
215 {
216   time_t mtime;
217   struct stat finfo;
218   char *file;
219
220   file = mailfiles[i]->name;
221   mtime = mailfiles[i]->mod_time;
222
223   if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0))
224     return (mtime != finfo.st_mtime);
225
226   return (0);
227 }
228
229 /* Return non-zero if FILE's access date has changed. */
230 static int
231 file_access_date_changed (i)
232      int i;
233 {
234   time_t atime;
235   struct stat finfo;
236   char *file;
237
238   file = mailfiles[i]->name;
239   atime = mailfiles[i]->access_time;
240
241   if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0))
242     return (atime != finfo.st_atime);
243
244   return (0);
245 }
246
247 /* Return non-zero if FILE's size has increased. */
248 static int
249 file_has_grown (i)
250      int i;
251 {
252   off_t size;
253   struct stat finfo;
254   char *file;
255
256   file = mailfiles[i]->name;
257   size = mailfiles[i]->file_size;
258
259   return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size));
260 }
261
262 /* Take an element from $MAILPATH and return the portion from
263    the first unquoted `?' or `%' to the end of the string.  This is the
264    message to be printed when the file contents change. */
265 static char *
266 parse_mailpath_spec (str)
267      char *str;
268 {
269   char *s;
270   int pass_next;
271
272   for (s = str, pass_next = 0; s && *s; s++)
273     {
274       if (pass_next)
275         {
276           pass_next = 0;
277           continue;
278         }
279       if (*s == '\\')
280         {
281           pass_next++;
282           continue;
283         }
284       if (*s == '?' || *s == '%')
285         return s;
286     }
287   return ((char *)NULL);
288 }
289
290 char *
291 make_default_mailpath ()
292 {
293 #if defined (DEFAULT_MAIL_DIRECTORY)
294   char *mp;
295
296   get_current_user_info ();
297   mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name));
298   strcpy (mp, DEFAULT_MAIL_DIRECTORY);
299   mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/';
300   strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name);
301   return (mp);
302 #else
303   return ((char *)NULL);
304 #endif
305 }
306
307 /* Remember the dates of the files specified by MAILPATH, or if there is
308    no MAILPATH, by the file specified in MAIL.  If neither exists, use a
309    default value, which we randomly concoct from using Unix. */
310 void
311 remember_mail_dates ()
312 {
313   char *mailpaths;
314   char *mailfile, *mp;
315   int i = 0;
316
317   mailpaths = get_string_value ("MAILPATH");
318
319   /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */
320   if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL")))
321     {
322       add_mail_file (mailpaths, (char *)NULL);
323       return;
324     }
325
326   if (mailpaths == 0)
327     {
328       mailpaths = make_default_mailpath ();
329       if (mailpaths)
330         {
331           add_mail_file (mailpaths, (char *)NULL);
332           free (mailpaths);
333         }
334       return;
335     }
336
337   while (mailfile = extract_colon_unit (mailpaths, &i))
338     {
339       mp = parse_mailpath_spec (mailfile);
340       if (mp && *mp)
341         *mp++ = '\0';
342       add_mail_file (mailfile, mp);
343       free (mailfile);
344     }
345 }
346
347 /* check_mail () is useful for more than just checking mail.  Since it has
348    the paranoids dream ability of telling you when someone has read your
349    mail, it can just as easily be used to tell you when someones .profile
350    file has been read, thus letting one know when someone else has logged
351    in.  Pretty good, huh? */
352
353 /* Check for mail in some files.  If the modification date of any
354    of the files in MAILPATH has changed since we last did a
355    remember_mail_dates () then mention that the user has mail.
356    Special hack:  If the variable MAIL_WARNING is non-zero and the
357    mail file has been accessed since the last time we remembered, then
358    the message "The mail in <mailfile> has been read" is printed. */
359 void
360 check_mail ()
361 {
362   char *current_mail_file, *message;
363   int i, use_user_notification;
364   char *dollar_underscore, *temp;
365
366   dollar_underscore = get_string_value ("_");
367   if (dollar_underscore)
368     dollar_underscore = savestring (dollar_underscore);
369
370   for (i = 0; i < mailfiles_count; i++)
371     {
372       current_mail_file = mailfiles[i]->name;
373
374       if (*current_mail_file == '\0')
375         continue;
376
377       if (file_mod_date_changed (i))
378         {
379           int file_is_bigger;
380
381           use_user_notification = mailfiles[i]->msg != (char *)NULL;
382           message = mailfiles[i]->msg ? mailfiles[i]->msg : _("You have mail in $_");
383
384           bind_variable ("_", current_mail_file);
385
386 #define atime mailfiles[i]->access_time
387 #define mtime mailfiles[i]->mod_time
388
389           /* Have to compute this before the call to update_mail_file, which
390              resets all the information. */
391           file_is_bigger = file_has_grown (i);
392
393           update_mail_file (i);
394
395           /* If the user has just run a program which manipulates the
396              mail file, then don't bother explaining that the mail
397              file has been manipulated.  Since some systems don't change
398              the access time to be equal to the modification time when
399              the mail in the file is manipulated, check the size also.  If
400              the file has not grown, continue. */
401           if ((atime >= mtime) && !file_is_bigger)
402             continue;
403
404           /* If the mod time is later than the access time and the file
405              has grown, note the fact that this is *new* mail. */
406           if (use_user_notification == 0 && (atime < mtime) && file_is_bigger)
407             message = _("You have new mail in $_");
408 #undef atime
409 #undef mtime
410
411           if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES))
412             {
413               puts (temp);
414               free (temp);
415             }
416           else
417             putchar ('\n');
418         }
419
420       if (mail_warning && file_access_date_changed (i))
421         {
422           update_mail_file (i);
423           printf (_("The mail in %s has been read\n"), current_mail_file);
424         }
425     }
426
427   if (dollar_underscore)
428     {
429       bind_variable ("_", dollar_underscore);
430       free (dollar_underscore);
431     }
432   else
433     unbind_variable ("_");
434 }