Git fork
at reftables-rust 377 lines 7.6 kB view raw
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 8#define DISABLE_SIGN_COMPARE_WARNINGS 9 10#include "builtin.h" 11#include "gettext.h" 12#include "string-list.h" 13#include "strbuf.h" 14 15static const char git_mailsplit_usage[] = 16"git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]"; 17 18static int is_from_line(const char *line, int len) 19{ 20 const char *colon; 21 22 if (len < 20 || memcmp("From ", line, 5)) 23 return 0; 24 25 colon = line + len - 2; 26 line += 5; 27 for (;;) { 28 if (colon < line) 29 return 0; 30 if (*--colon == ':') 31 break; 32 } 33 34 if (!isdigit(colon[-4]) || 35 !isdigit(colon[-2]) || 36 !isdigit(colon[-1]) || 37 !isdigit(colon[ 1]) || 38 !isdigit(colon[ 2])) 39 return 0; 40 41 /* year */ 42 if (strtol(colon+3, NULL, 10) <= 90) 43 return 0; 44 45 /* Ok, close enough */ 46 return 1; 47} 48 49static struct strbuf buf = STRBUF_INIT; 50static int keep_cr; 51static int mboxrd; 52 53static int is_gtfrom(const struct strbuf *buf) 54{ 55 size_t min = strlen(">From "); 56 size_t ngt; 57 58 if (buf->len < min) 59 return 0; 60 61 ngt = strspn(buf->buf, ">"); 62 return ngt && starts_with(buf->buf + ngt, "From "); 63} 64 65/* Called with the first line (potentially partial) 66 * already in buf[] -- normally that should begin with 67 * the Unix "From " line. Write it into the specified 68 * file. 69 */ 70static int split_one(FILE *mbox, const char *name, int allow_bare) 71{ 72 FILE *output; 73 int fd; 74 int status = 0; 75 int is_bare = !is_from_line(buf.buf, buf.len); 76 77 if (is_bare && !allow_bare) { 78 fprintf(stderr, "corrupt mailbox\n"); 79 exit(1); 80 } 81 fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666); 82 output = xfdopen(fd, "w"); 83 84 /* Copy it out, while searching for a line that begins with 85 * "From " and having something that looks like a date format. 86 */ 87 for (;;) { 88 if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' && 89 buf.buf[buf.len-2] == '\r') { 90 strbuf_setlen(&buf, buf.len-2); 91 strbuf_addch(&buf, '\n'); 92 } 93 94 if (mboxrd && is_gtfrom(&buf)) 95 strbuf_remove(&buf, 0, 1); 96 97 if (fwrite(buf.buf, 1, buf.len, output) != buf.len) 98 die_errno("cannot write output"); 99 100 if (strbuf_getwholeline(&buf, mbox, '\n')) { 101 if (feof(mbox)) { 102 status = 1; 103 break; 104 } 105 die_errno("cannot read mbox"); 106 } 107 if (!is_bare && is_from_line(buf.buf, buf.len)) 108 break; /* done with one message */ 109 } 110 fclose(output); 111 return status; 112} 113 114static int populate_maildir_list(struct string_list *list, const char *path) 115{ 116 DIR *dir; 117 struct dirent *dent; 118 char *name = NULL; 119 const char *subs[] = { "cur", "new", NULL }; 120 const char **sub; 121 int ret = -1; 122 123 for (sub = subs; *sub; ++sub) { 124 free(name); 125 name = xstrfmt("%s/%s", path, *sub); 126 if (!(dir = opendir(name))) { 127 if (errno == ENOENT) 128 continue; 129 error_errno("cannot opendir %s", name); 130 goto out; 131 } 132 133 while ((dent = readdir(dir)) != NULL) { 134 if (dent->d_name[0] == '.') 135 continue; 136 free(name); 137 name = xstrfmt("%s/%s", *sub, dent->d_name); 138 string_list_insert(list, name); 139 } 140 141 closedir(dir); 142 } 143 144 ret = 0; 145 146out: 147 free(name); 148 return ret; 149} 150 151static int maildir_filename_cmp(const char *a, const char *b) 152{ 153 while (*a && *b) { 154 if (isdigit(*a) && isdigit(*b)) { 155 long int na, nb; 156 na = strtol(a, (char **)&a, 10); 157 nb = strtol(b, (char **)&b, 10); 158 if (na != nb) 159 return na - nb; 160 /* strtol advanced our pointers */ 161 } 162 else { 163 if (*a != *b) 164 return (unsigned char)*a - (unsigned char)*b; 165 a++; 166 b++; 167 } 168 } 169 return (unsigned char)*a - (unsigned char)*b; 170} 171 172static int split_maildir(const char *maildir, const char *dir, 173 int nr_prec, int skip) 174{ 175 char *file = NULL; 176 FILE *f = NULL; 177 int ret = -1; 178 struct string_list list = STRING_LIST_INIT_DUP; 179 180 list.cmp = maildir_filename_cmp; 181 182 if (populate_maildir_list(&list, maildir) < 0) 183 goto out; 184 185 for (size_t i = 0; i < list.nr; i++) { 186 char *name; 187 188 free(file); 189 file = xstrfmt("%s/%s", maildir, list.items[i].string); 190 191 f = fopen(file, "r"); 192 if (!f) { 193 error_errno("cannot open mail %s", file); 194 goto out; 195 } 196 197 if (strbuf_getwholeline(&buf, f, '\n')) { 198 error_errno("cannot read mail %s", file); 199 goto out; 200 } 201 202 name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); 203 split_one(f, name, 1); 204 free(name); 205 206 fclose(f); 207 f = NULL; 208 } 209 210 ret = skip; 211out: 212 if (f) 213 fclose(f); 214 free(file); 215 string_list_clear(&list, 1); 216 return ret; 217} 218 219static int split_mbox(const char *file, const char *dir, int allow_bare, 220 int nr_prec, int skip) 221{ 222 int ret = -1; 223 int peek; 224 225 FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); 226 int file_done = 0; 227 228 if (isatty(fileno(f))) 229 warning(_("reading patches from stdin/tty...")); 230 231 if (!f) { 232 error_errno("cannot open mbox %s", file); 233 goto out; 234 } 235 236 do { 237 peek = fgetc(f); 238 if (peek == EOF) { 239 if (f == stdin) 240 /* empty stdin is OK */ 241 ret = skip; 242 else { 243 fclose(f); 244 error(_("empty mbox: '%s'"), file); 245 } 246 goto out; 247 } 248 } while (isspace(peek)); 249 ungetc(peek, f); 250 251 if (strbuf_getwholeline(&buf, f, '\n')) { 252 /* empty stdin is OK */ 253 if (f != stdin) { 254 error("cannot read mbox %s", file); 255 goto out; 256 } 257 file_done = 1; 258 } 259 260 while (!file_done) { 261 char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip); 262 file_done = split_one(f, name, allow_bare); 263 free(name); 264 } 265 266 if (f != stdin) 267 fclose(f); 268 269 ret = skip; 270out: 271 return ret; 272} 273 274int cmd_mailsplit(int argc, 275 const char **argv, 276 const char *prefix, 277 struct repository *repo UNUSED) 278{ 279 int nr = 0, nr_prec = 4, num = 0; 280 int allow_bare = 0; 281 const char *dir = NULL; 282 const char **argp; 283 static const char *stdin_only[] = { "-", NULL }; 284 285 BUG_ON_NON_EMPTY_PREFIX(prefix); 286 287 show_usage_if_asked(argc, argv, git_mailsplit_usage); 288 289 for (argp = argv+1; *argp; argp++) { 290 const char *arg = *argp; 291 292 if (arg[0] != '-') 293 break; 294 /* do flags here */ 295 if ( arg[1] == 'd' ) { 296 nr_prec = strtol(arg+2, NULL, 10); 297 if (nr_prec < 3 || 10 <= nr_prec) 298 usage(git_mailsplit_usage); 299 continue; 300 } else if ( arg[1] == 'f' ) { 301 nr = strtol(arg+2, NULL, 10); 302 } else if ( arg[1] == 'b' && !arg[2] ) { 303 allow_bare = 1; 304 } else if (!strcmp(arg, "--keep-cr")) { 305 keep_cr = 1; 306 } else if ( arg[1] == 'o' && arg[2] ) { 307 dir = arg+2; 308 } else if (!strcmp(arg, "--mboxrd")) { 309 mboxrd = 1; 310 } else if ( arg[1] == '-' && !arg[2] ) { 311 argp++; /* -- marks end of options */ 312 break; 313 } else { 314 die("unknown option: %s", arg); 315 } 316 } 317 318 if ( !dir ) { 319 /* Backwards compatibility: if no -o specified, accept 320 <mbox> <dir> or just <dir> */ 321 switch (argc - (argp-argv)) { 322 case 1: 323 dir = argp[0]; 324 argp = stdin_only; 325 break; 326 case 2: 327 stdin_only[0] = argp[0]; 328 dir = argp[1]; 329 argp = stdin_only; 330 break; 331 default: 332 usage(git_mailsplit_usage); 333 } 334 } else { 335 /* New usage: if no more argument, parse stdin */ 336 if ( !*argp ) 337 argp = stdin_only; 338 } 339 340 while (*argp) { 341 const char *arg = *argp++; 342 struct stat argstat; 343 int ret = 0; 344 345 if (arg[0] == '-' && arg[1] == 0) { 346 ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); 347 if (ret < 0) { 348 error("cannot split patches from stdin"); 349 return 1; 350 } 351 num += (ret - nr); 352 nr = ret; 353 continue; 354 } 355 356 if (stat(arg, &argstat) == -1) { 357 error_errno("cannot stat %s", arg); 358 return 1; 359 } 360 361 if (S_ISDIR(argstat.st_mode)) 362 ret = split_maildir(arg, dir, nr_prec, nr); 363 else 364 ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); 365 366 if (ret < 0) { 367 error("cannot split patches from %s", arg); 368 return 1; 369 } 370 num += (ret - nr); 371 nr = ret; 372 } 373 374 printf("%d\n", num); 375 376 return 0; 377}