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