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