mailsplit: allow feeding mbox from standard input.
[git.git] / mailsplit.c
1 /*
2  * Totally braindamaged mbox splitter program.
3  *
4  * It just splits a mbox into a list of files: "0001" "0002" ..
5  * so you can process them further from there.
6  */
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <fcntl.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/mman.h>
13 #include <string.h>
14 #include <stdio.h>
15 #include <ctype.h>
16 #include <assert.h>
17 #include "cache.h"
18
19 static const char git_mailsplit_usage[] =
20 "git-mailsplit [-d<prec>] [<mbox>] <directory>";
21
22 static int is_from_line(const char *line, int len)
23 {
24         const char *colon;
25
26         if (len < 20 || memcmp("From ", line, 5))
27                 return 0;
28
29         colon = line + len - 2;
30         line += 5;
31         for (;;) {
32                 if (colon < line)
33                         return 0;
34                 if (*--colon == ':')
35                         break;
36         }
37
38         if (!isdigit(colon[-4]) ||
39             !isdigit(colon[-2]) ||
40             !isdigit(colon[-1]) ||
41             !isdigit(colon[ 1]) ||
42             !isdigit(colon[ 2]))
43                 return 0;
44
45         /* year */
46         if (strtol(colon+3, NULL, 10) <= 90)
47                 return 0;
48
49         /* Ok, close enough */
50         return 1;
51 }
52
53 /* Could be as small as 64, enough to hold a Unix "From " line. */
54 static char buf[4096];
55
56 /* Called with the first line (potentially partial)
57  * already in buf[] -- normally that should begin with
58  * the Unix "From " line.  Write it into the specified
59  * file.
60  */
61 static int split_one(FILE *mbox, const char *name)
62 {
63         FILE *output = NULL;
64         int len = strlen(buf);
65         int fd;
66         int status = 0;
67
68         if (!is_from_line(buf, len))
69                 goto corrupt;
70
71         fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
72         if (fd < 0)
73                 die("cannot open output file %s", name);
74         output = fdopen(fd, "w");
75
76         /* Copy it out, while searching for a line that begins with
77          * "From " and having something that looks like a date format.
78          */
79         for (;;) {
80                 int is_partial = (buf[len-1] != '\n');
81
82                 if (fputs(buf, output) == EOF)
83                         die("cannot write output");
84
85                 if (fgets(buf, sizeof(buf), mbox) == NULL) {
86                         if (feof(mbox)) {
87                                 status = 1;
88                                 break;
89                         }
90                         die("cannot read mbox");
91                 }
92                 len = strlen(buf);
93                 if (!is_partial && is_from_line(buf, len))
94                         break; /* done with one message */
95         }
96         fclose(output);
97         return status;
98
99  corrupt:
100         if (output)
101                 fclose(output);
102         unlink(name);
103         fprintf(stderr, "corrupt mailbox\n");
104         exit(1);
105 }
106
107 int main(int argc, const char **argv)
108 {
109         int i, nr, nr_prec = 4;
110         FILE *mbox = NULL;
111
112         for (i = 1; i < argc; i++) {
113                 const char *arg = argv[i];
114
115                 if (arg[0] != '-')
116                         break;
117                 /* do flags here */
118                 if (!strncmp(arg, "-d", 2)) {
119                         nr_prec = strtol(arg + 2, NULL, 10);
120                         if (nr_prec < 3 || 10 <= nr_prec)
121                                 usage(git_mailsplit_usage);
122                         continue;
123                 }
124         }
125
126         /* Either one remaining arg (dir), or two (mbox and dir) */
127         switch (argc - i) {
128         case 1:
129                 mbox = stdin;
130                 break;
131         case 2:
132                 if ((mbox = fopen(argv[i], "r")) == NULL)
133                         die("cannot open mbox %s for reading", argv[i]);
134                 break;
135         default:
136                 usage(git_mailsplit_usage);
137         }
138         if (chdir(argv[argc - 1]) < 0)
139                 usage(git_mailsplit_usage);
140
141         nr = 0;
142         if (fgets(buf, sizeof(buf), mbox) == NULL)
143                 die("cannot read mbox");
144
145         for (;;) {
146                 char name[10];
147
148                 sprintf(name, "%0*d", nr_prec, ++nr);
149                 switch (split_one(mbox, name)) {
150                 case 0:
151                         break;
152                 case 1:
153                         printf("%d\n", nr);
154                         return 0;
155                 default:
156                         exit(1);
157                 }
158         }
159 }