summaryrefslogtreecommitdiffstats
path: root/lib/sh/mailstat.c
blob: bd5c25fb4c021d252b974b4369c671b14d668e14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* mailstat.c -- stat a mailbox file, handling maildir-type mail directories */

/* Copyright (C) 2001 Free Software Foundation, Inc.

   This file is part of GNU Bash, the Bourne Again SHell.

   Bash is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Bash is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>
 
#include <stdio.h>
#include <errno.h>

#include <bashtypes.h>
#include <posixstat.h>
#include <posixdir.h>
#include <bashansi.h>

#if defined (HAVE_SYS_PARAM_H)
#  include <sys/param.h>
#endif

#include <maxpath.h>

/*
 *     Stat a file. If it's a maildir, check all messages
 *     in the maildir and present the grand total as a file.
 *     The fields in the 'struct stat' are from the mail directory.
 *     The following fields are emulated:
 *
 *     st_nlink	always 1, unless st_blocks is not present, in which case it's
 *		the total number of messages
 *     st_size	 total number of bytes in all files
 *     st_blocks       total number of messages, if present in struct stat
 *     st_atime	access time of newest file in maildir
 *     st_mtime	modify time of newest file in maildir
 *     st_mode	 S_IFDIR changed to S_IFREG
 *
 *     This is good enough for most mail-checking applications.
 */

int
mailstat(path, st)
     const char *path;
     struct stat *st;
{
  static struct stat st_new_last, st_ret_last;
  struct stat st_ret, st_tmp;
  DIR *dd;
  struct dirent *fn;
  char dir[PATH_MAX * 2], file[PATH_MAX * 2 + 1];
  int i, l;
  time_t atime, mtime;

  atime = mtime = 0;

  /* First see if it's a directory. */
  if ((i = stat(path, st)) != 0 || S_ISDIR(st->st_mode) == 0)
    return i;

  if (strlen(path) > sizeof(dir) - 5)
    {
#ifdef ENAMETOOLONG
      errno = ENAMETOOLONG;
#else
      errno = EINVAL;
#endif
      return -1;
    }

  st_ret = *st;
  st_ret.st_nlink = 1;
  st_ret.st_size  = 0;
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
  st_ret.st_blocks  = 0;
#else
  st_ret.st_nlink = 0;
#endif
  st_ret.st_mode  &= ~S_IFDIR;
  st_ret.st_mode  |= S_IFREG;

  /* See if cur/ is present */
  sprintf(dir, "%s/cur", path);
  if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0)
    return 0;
  st_ret.st_atime = st_tmp.st_atime;

  /* See if tmp/ is present */
  sprintf(dir, "%s/tmp", path);
  if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0)
    return 0;
  st_ret.st_mtime = st_tmp.st_mtime;

  /* And new/ */
  sprintf(dir, "%s/new", path);
  if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0)
    return 0;
  st_ret.st_mtime = st_tmp.st_mtime;

  /* Optimization - if new/ didn't change, nothing else did. */
  if (st_tmp.st_dev == st_new_last.st_dev &&
      st_tmp.st_ino == st_new_last.st_ino &&
      st_tmp.st_atime == st_new_last.st_atime &&
      st_tmp.st_mtime == st_new_last.st_mtime)
    {
      *st = st_ret_last;
      return 0;
    }
  st_new_last = st_tmp;

  /* Loop over new/ and cur/ */
  for (i = 0; i < 2; i++)
    {
      sprintf(dir, "%s/%s", path, i ? "cur" : "new");
      sprintf(file, "%s/", dir);
      l = strlen(file);
      if ((dd = opendir(dir)) == NULL)
	return 0;
      while ((fn = readdir(dd)) != NULL)
	{
	  if (fn->d_name[0] == '.' || strlen(fn->d_name) + l >= sizeof(file))
	    continue;
	  strcpy(file + l, fn->d_name);
	  if (stat(file, &st_tmp) != 0)
	    continue;
	  st_ret.st_size += st_tmp.st_size;
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
	  st_ret.st_blocks++;
#else
	  st_ret.st_nlink++;
#endif
	  if (st_tmp.st_atime != st_tmp.st_mtime && st_tmp.st_atime > atime)
	    atime = st_tmp.st_atime;
	  if (st_tmp.st_mtime > mtime)
	    mtime = st_tmp.st_mtime;
	}
      closedir(dd);
    }

/*  if (atime) */	/* Set atime even if cur/ is empty */
      st_ret.st_atime = atime;
    if (mtime)
      st_ret.st_mtime = mtime;

    *st = st_ret_last = st_ret;
    return 0;
}