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