Andreas Krennmair:
[apps/madmutt.git] / compress.c
1 /*
2  * Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include "mutt.h"
20
21 #ifdef USE_COMPRESSED
22
23 #include "mx.h"
24 #include "mailbox.h"
25 #include "mutt_curses.h"
26
27 #include <errno.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31
32 typedef struct {
33   const char *close;            /* close-hook  command */
34   const char *open;             /* open-hook   command */
35   const char *append;           /* append-hook command */
36   off_t size;                   /* size of real folder */
37 } COMPRESS_INFO;
38
39 char echo_cmd[HUGE_STRING];
40
41 /* parameters:
42  * ctx - context to lock
43  * excl - exclusive lock?
44  * retry - should retry if unable to lock?
45  */
46 int mbox_lock_compressed (CONTEXT * ctx, FILE * fp, int excl, int retry)
47 {
48   int r;
49
50   if ((r = mx_lock_file (ctx->realpath, fileno (fp), excl, 1, retry)) == 0)
51     ctx->locked = 1;
52   else if (retry && !excl) {
53     ctx->readonly = 1;
54     return 0;
55   }
56
57   return (r);
58 }
59
60 void mbox_unlock_compressed (CONTEXT * ctx, FILE * fp)
61 {
62   if (ctx->locked) {
63     fflush (fp);
64
65     mx_unlock_file (ctx->realpath, fileno (fp), 1);
66     ctx->locked = 0;
67   }
68 }
69
70 static int is_new (const char *path)
71 {
72   return (access (path, W_OK) != 0 && errno == ENOENT) ? 1 : 0;
73 }
74
75 static const char *find_compress_hook (int type, const char *path)
76 {
77   const char *c = mutt_find_hook (type, path);
78
79   return (!c || !*c) ? NULL : c;
80 }
81
82 int mutt_can_read_compressed (const char *path)
83 {
84   return find_compress_hook (M_OPENHOOK, path) ? 1 : 0;
85 }
86
87 /* if the file is new, we really do not append, but create, and so use
88  * close-hook, and not append-hook 
89  */
90 static const char *get_append_command (const char *path, const CONTEXT * ctx)
91 {
92   COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
93
94   return (is_new (path)) ? ci->close : ci->append;
95 }
96
97 int mutt_can_append_compressed (const char *path)
98 {
99   int magic;
100
101   if (is_new (path))
102     return (find_compress_hook (M_CLOSEHOOK, path) ? 1 : 0);
103
104   magic = mx_get_magic (path);
105
106   if (magic != 0 && magic != M_COMPRESSED)
107     return 0;
108
109   return (find_compress_hook (M_APPENDHOOK, path)
110           || (find_compress_hook (M_OPENHOOK, path)
111               && find_compress_hook (M_CLOSEHOOK, path))) ? 1 : 0;
112 }
113
114 /* open a compressed mailbox */
115 static COMPRESS_INFO *set_compress_info (CONTEXT * ctx)
116 {
117   COMPRESS_INFO *ci;
118
119   /* Now lets uncompress this thing */
120   ci = safe_malloc (sizeof (COMPRESS_INFO));
121   ctx->compressinfo = (void *) ci;
122   ci->append = find_compress_hook (M_APPENDHOOK, ctx->path);
123   ci->open = find_compress_hook (M_OPENHOOK, ctx->path);
124   ci->close = find_compress_hook (M_CLOSEHOOK, ctx->path);
125   return ci;
126 }
127
128 static void set_path (CONTEXT * ctx)
129 {
130   char tmppath[_POSIX_PATH_MAX];
131
132   /* Setup the right paths */
133   ctx->realpath = ctx->path;
134
135   /* Uncompress to /tmp */
136   mutt_mktemp (tmppath);
137   ctx->path = safe_malloc (strlen (tmppath) + 1);
138   strcpy (ctx->path, tmppath);
139 }
140
141 static int get_size (const char *path)
142 {
143   struct stat sb;
144
145   if (stat (path, &sb) != 0)
146     return 0;
147   return (sb.st_size);
148 }
149
150 static void store_size (CONTEXT * ctx)
151 {
152   COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
153
154   ci->size = get_size (ctx->realpath);
155 }
156
157 static const char *compresshook_format_str (char *dest, size_t destlen,
158                                             char op, const char *src,
159                                             const char *fmt,
160                                             const char *ifstring,
161                                             const char *elsestring,
162                                             unsigned long data,
163                                             format_flag flags)
164 {
165   char tmp[SHORT_STRING];
166
167   CONTEXT *ctx = (CONTEXT *) data;
168
169   switch (op) {
170   case 'f':
171     snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
172     snprintf (dest, destlen, tmp, ctx->realpath);
173     break;
174   case 't':
175     snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
176     snprintf (dest, destlen, tmp, ctx->path);
177     break;
178   }
179   return (src);
180 }
181
182 /* check that the command has both %f and %t
183  * 0 means OK, -1 means error
184  */
185 int mutt_test_compress_command (const char *cmd)
186 {
187   return (strstr (cmd, "%f") && strstr (cmd, "%t")) ? 0 : -1;
188 }
189
190 static char *get_compression_cmd (const char *cmd, const CONTEXT * ctx)
191 {
192   char expanded[_POSIX_PATH_MAX];
193
194   mutt_FormatString (expanded, sizeof (expanded), cmd,
195                      compresshook_format_str, (unsigned long) ctx, 0);
196   return safe_strdup (expanded);
197 }
198
199 int mutt_check_mailbox_compressed (CONTEXT * ctx)
200 {
201   COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
202
203   if (ci->size != get_size (ctx->realpath)) {
204     FREE (&ctx->compressinfo);
205     FREE (&ctx->realpath);
206     mutt_error _("Mailbox was corrupted!");
207
208     return (-1);
209   }
210   return (0);
211 }
212
213 int mutt_open_read_compressed (CONTEXT * ctx)
214 {
215   char *cmd;
216   FILE *fp;
217   int rc;
218
219   COMPRESS_INFO *ci = set_compress_info (ctx);
220
221   if (!ci->open) {
222     ctx->magic = 0;
223     FREE (ctx->compressinfo);
224     return (-1);
225   }
226   if (!ci->close || access (ctx->path, W_OK) != 0)
227     ctx->readonly = 1;
228
229   set_path (ctx);
230   store_size (ctx);
231
232   if (!ctx->quiet)
233     mutt_message (_("Decompressing %s..."), ctx->realpath);
234
235   cmd = get_compression_cmd (ci->open, ctx);
236   if (cmd == NULL)
237     return (-1);
238   dprint (2, (debugfile, "DecompressCmd: '%s'\n", cmd));
239
240   if ((fp = fopen (ctx->realpath, "r")) == NULL) {
241     mutt_perror (ctx->realpath);
242     FREE (&cmd);
243     return (-1);
244   }
245   mutt_block_signals ();
246   if (mbox_lock_compressed (ctx, fp, 0, 1) == -1) {
247     fclose (fp);
248     mutt_unblock_signals ();
249     mutt_error _("Unable to lock mailbox!");
250
251     FREE (&cmd);
252     return (-1);
253   }
254
255   endwin ();
256   fflush (stdout);
257   sprintf (echo_cmd, _("echo Decompressing %s..."), ctx->realpath);
258   mutt_system (echo_cmd);
259   rc = mutt_system (cmd);
260   mbox_unlock_compressed (ctx, fp);
261   mutt_unblock_signals ();
262   fclose (fp);
263
264   if (rc) {
265     mutt_any_key_to_continue (NULL);
266     ctx->magic = 0;
267     FREE (ctx->compressinfo);
268     mutt_error (_("Error executing: %s : unable to open the mailbox!\n"),
269                 cmd);
270   }
271   FREE (&cmd);
272   if (rc)
273     return (-1);
274
275   if (mutt_check_mailbox_compressed (ctx))
276     return (-1);
277
278   ctx->magic = mx_get_magic (ctx->path);
279
280   return (0);
281 }
282
283 void restore_path (CONTEXT * ctx)
284 {
285   FREE (&ctx->path);
286   ctx->path = ctx->realpath;
287 }
288
289 /* remove the temporary mailbox */
290 void remove_file (CONTEXT * ctx)
291 {
292   if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
293     remove (ctx->path);
294 }
295
296 int mutt_open_append_compressed (CONTEXT * ctx)
297 {
298   FILE *fh;
299   COMPRESS_INFO *ci = set_compress_info (ctx);
300
301   if (!get_append_command (ctx->path, ctx)) {
302     if (ci->open && ci->close)
303       return (mutt_open_read_compressed (ctx));
304
305     ctx->magic = 0;
306     FREE (&ctx->compressinfo);
307     return (-1);
308   }
309
310   set_path (ctx);
311
312   ctx->magic = DefaultMagic;
313
314   if (!is_new (ctx->realpath))
315     if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
316       if ((fh = safe_fopen (ctx->path, "w")))
317         fclose (fh);
318   /* No error checking - the parent function will catch it */
319
320   return (0);
321 }
322
323 /* close a compressed mailbox */
324 void mutt_fast_close_compressed (CONTEXT * ctx)
325 {
326   dprint (2, (debugfile, "mutt_fast_close_compressed called on '%s'\n",
327               ctx->path));
328
329   if (ctx->compressinfo) {
330     if (ctx->fp)
331       fclose (ctx->fp);
332     ctx->fp = NULL;
333     /* if the folder was removed, remove the gzipped folder too */
334     if (access (ctx->path, F_OK) != 0 && !option (OPTSAVEEMPTY))
335       remove (ctx->realpath);
336     else
337       remove_file (ctx);
338
339     restore_path (ctx);
340     FREE (&ctx->compressinfo);
341   }
342 }
343
344 /* return 0 on success, -1 on failure */
345 int mutt_sync_compressed (CONTEXT * ctx)
346 {
347   char *cmd;
348   int rc = 0;
349   FILE *fp;
350   COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
351
352   if (!ctx->quiet)
353     mutt_message (_("Compressing %s..."), ctx->realpath);
354
355   cmd = get_compression_cmd (ci->close, ctx);
356   if (cmd == NULL)
357     return (-1);
358
359   if ((fp = fopen (ctx->realpath, "a")) == NULL) {
360     mutt_perror (ctx->realpath);
361     FREE (&cmd);
362     return (-1);
363   }
364   mutt_block_signals ();
365   if (mbox_lock_compressed (ctx, fp, 1, 1) == -1) {
366     fclose (fp);
367     mutt_unblock_signals ();
368     mutt_error _("Unable to lock mailbox!");
369
370     store_size (ctx);
371
372     FREE (&cmd);
373     return (-1);
374   }
375
376   dprint (2, (debugfile, "CompressCommand: '%s'\n", cmd));
377
378   endwin ();
379   fflush (stdout);
380   sprintf (echo_cmd, _("echo Compressing %s..."), ctx->realpath);
381   mutt_system (echo_cmd);
382   if (mutt_system (cmd)) {
383     mutt_any_key_to_continue (NULL);
384     mutt_error (_
385                 ("%s: Error compressing mailbox! Original mailbox deleted, uncompressed one kept!\n"),
386                 ctx->path);
387     rc = -1;
388   }
389
390   mbox_unlock_compressed (ctx, fp);
391   mutt_unblock_signals ();
392   fclose (fp);
393
394   FREE (&cmd);
395
396   store_size (ctx);
397
398   return (rc);
399 }
400
401 int mutt_slow_close_compressed (CONTEXT * ctx)
402 {
403   FILE *fp;
404   const char *append;
405   char *cmd;
406   COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
407
408   dprint (2, (debugfile, "mutt_slow_close_compressed called on '%s'\n",
409               ctx->path));
410
411   if (!(ctx->append && ((append = get_append_command (ctx->realpath, ctx))
412                         || (append = ci->close)))) {    /* if we can not or should not append,
413                                                          * we only have to remove the compressed info, because sync was already
414                                                          * called 
415                                                          */
416     mutt_fast_close_compressed (ctx);
417     return (0);
418   }
419
420   if (ctx->fp)
421     fclose (ctx->fp);
422   ctx->fp = NULL;
423
424   if (!ctx->quiet) {
425     if (append == ci->close)
426       mutt_message (_("Compressing %s..."), ctx->realpath);
427     else
428       mutt_message (_("Compressed-appending to %s..."), ctx->realpath);
429   }
430
431   cmd = get_compression_cmd (append, ctx);
432   if (cmd == NULL)
433     return (-1);
434
435   if ((fp = fopen (ctx->realpath, "a")) == NULL) {
436     mutt_perror (ctx->realpath);
437     FREE (&cmd);
438     return (-1);
439   }
440   mutt_block_signals ();
441   if (mbox_lock_compressed (ctx, fp, 1, 1) == -1) {
442     fclose (fp);
443     mutt_unblock_signals ();
444     mutt_error _("Unable to lock mailbox!");
445
446     FREE (&cmd);
447     return (-1);
448   }
449
450   dprint (2, (debugfile, "CompressCmd: '%s'\n", cmd));
451
452   endwin ();
453   fflush (stdout);
454
455   if (append == ci->close)
456     sprintf (echo_cmd, _("echo Compressing %s..."), ctx->realpath);
457   else
458     sprintf (echo_cmd, _("echo Compressed-appending to %s..."),
459              ctx->realpath);
460   mutt_system (echo_cmd);
461
462   if (mutt_system (cmd)) {
463     mutt_any_key_to_continue (NULL);
464     mutt_error (_
465                 (" %s: Error compressing mailbox!  Uncompressed one kept!\n"),
466                 ctx->path);
467     FREE (&cmd);
468     mbox_unlock_compressed (ctx, fp);
469     mutt_unblock_signals ();
470     fclose (fp);
471     return (-1);
472   }
473
474   mbox_unlock_compressed (ctx, fp);
475   mutt_unblock_signals ();
476   fclose (fp);
477   remove_file (ctx);
478   restore_path (ctx);
479   FREE (&cmd);
480   FREE (&ctx->compressinfo);
481
482   return (0);
483 }
484
485 #endif /* USE_COMPRESSED */