example/solv: support all compression types
[platform/upstream/libsolv.git] / ext / solv_xfopen.c
1 /*
2  * Copyright (c) 2011, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 #define _GNU_SOURCE
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <zlib.h>
14 #include <fcntl.h>
15
16 #include "solv_xfopen.h"
17 #include "util.h"
18
19
20 static FILE *cookieopen(void *cookie, const char *mode,
21         ssize_t (*cread)(void *, char *, size_t), 
22         ssize_t (*cwrite)(void *, const char *, size_t), 
23         int (*cclose)(void *))
24 {
25   if (!cookie)
26     return 0;
27 #ifdef HAVE_FUNOPEN
28   return funopen(cookie, 
29       (int (*)(void *, char *, int))(*mode == 'r' ? cread: NULL),/* readfn */
30       (int (*)(void *, const char *, int))(*mode == 'w' ? cwrite : NULL), /* writefn */
31       (fpos_t (*)(void *, fpos_t, int))NULL, /* seekfn */
32       cclose
33       );
34 #elif defined(HAVE_FOPENCOOKIE)
35   cookie_io_functions_t cio;
36   memset(&cio, 0, sizeof(cio));
37   if (*mode == 'r')
38     cio.read = cread;
39   else if (*mode == 'w')
40     cio.write = cwrite;
41   cio.close = cclose;
42   return  fopencookie(cookie, *mode == 'w' ? "w" : "r", cio);
43 #else
44 # error Need to implement custom I/O
45 #endif
46 }
47
48
49 /* gzip compression */
50
51 static ssize_t cookie_gzread(void *cookie, char *buf, size_t nbytes)
52 {
53   return gzread((gzFile)cookie, buf, nbytes);
54 }
55
56 static ssize_t cookie_gzwrite(void *cookie, const char *buf, size_t nbytes)
57 {
58   return gzwrite((gzFile)cookie, buf, nbytes);
59 }
60
61 static int cookie_gzclose(void *cookie)
62 {
63   return gzclose((gzFile)cookie);
64 }
65
66 static inline FILE *mygzfopen(const char *fn, const char *mode)
67 {
68   gzFile gzf = gzopen(fn, mode);
69   return cookieopen(gzf, mode, cookie_gzread, cookie_gzwrite, cookie_gzclose);
70 }
71
72 static inline FILE *mygzfdopen(int fd, const char *mode)
73 {
74   gzFile gzf = gzdopen(fd, mode);
75   return cookieopen(gzf, mode, cookie_gzread, cookie_gzwrite, cookie_gzclose);
76 }
77
78 #ifdef ENABLE_BZIP2_COMPRESSION
79
80 #include <bzlib.h>
81
82 /* bzip2 compression */
83
84 static ssize_t cookie_bzread(void *cookie, char *buf, size_t nbytes)
85 {
86   return BZ2_bzread((BZFILE *)cookie, buf, nbytes);
87 }
88
89 static ssize_t cookie_bzwrite(void *cookie, const char *buf, size_t nbytes)
90 {
91   return BZ2_bzwrite((BZFILE *)cookie, (char *)buf, nbytes);
92 }
93
94 static int cookie_bzclose(void *cookie)
95 {
96   BZ2_bzclose((BZFILE *)cookie);
97   return 0;
98 }
99
100 static inline FILE *mybzfopen(const char *fn, const char *mode)
101 {
102   BZFILE *bzf = BZ2_bzopen(fn, mode);
103   return cookieopen(bzf, mode, cookie_bzread, cookie_bzwrite, cookie_bzclose);
104 }
105
106 static inline FILE *mybzfdopen(int fd, const char *mode)
107 {
108   BZFILE *bzf = BZ2_bzdopen(fd, mode);
109   return cookieopen(bzf, mode, cookie_bzread, cookie_bzwrite, cookie_bzclose);
110 }
111
112 #endif
113
114
115 #ifdef ENABLE_LZMA_COMPRESSION
116
117 #include <lzma.h>
118
119 /* lzma code written by me in 2008 for rpm's rpmio.c */
120
121 typedef struct lzfile {
122   unsigned char buf[1 << 15];
123   lzma_stream strm;
124   FILE *file;
125   int encoding;
126   int eof;
127 } LZFILE;
128
129 static inline lzma_ret setup_alone_encoder(lzma_stream *strm, int level)
130 {
131   lzma_options_lzma options;
132   lzma_lzma_preset(&options, level);
133   return lzma_alone_encoder(strm, &options);
134 }
135
136 static lzma_stream stream_init = LZMA_STREAM_INIT;
137
138 static LZFILE *lzopen(const char *path, const char *mode, int fd, int isxz)
139 {
140   int level = 7;
141   int encoding = 0;
142   FILE *fp;
143   LZFILE *lzfile;
144   lzma_ret ret;
145
146   if (!path && fd < 0)
147     return 0;
148   for (; *mode; mode++)
149     {
150       if (*mode == 'w')
151         encoding = 1;
152       else if (*mode == 'r')
153         encoding = 0;
154       else if (*mode >= '1' && *mode <= '9')
155         level = *mode - '0';
156     }
157   if (fd != -1)
158     fp = fdopen(fd, encoding ? "w" : "r");
159   else
160     fp = fopen(path, encoding ? "w" : "r");
161   if (!fp)
162     return 0;
163   lzfile = calloc(1, sizeof(*lzfile));
164   if (!lzfile)
165     {
166       fclose(fp);
167       return 0;
168     }
169   lzfile->file = fp;
170   lzfile->encoding = encoding;
171   lzfile->eof = 0;
172   lzfile->strm = stream_init;
173   if (encoding)
174     {
175       if (isxz)
176         ret = lzma_easy_encoder(&lzfile->strm, level, LZMA_CHECK_SHA256);
177       else
178         ret = setup_alone_encoder(&lzfile->strm, level);
179     }
180   else
181     ret = lzma_auto_decoder(&lzfile->strm, 100 << 20, 0);
182   if (ret != LZMA_OK)
183     {
184       fclose(fp);
185       free(lzfile);
186       return 0;
187     }
188   return lzfile;
189 }
190
191 static int lzclose(void *cookie)
192 {
193   LZFILE *lzfile = cookie;
194   lzma_ret ret;
195   size_t n;
196   int rc;
197
198   if (!lzfile)
199     return -1;
200   if (lzfile->encoding)
201     {
202       for (;;)
203         {
204           lzfile->strm.avail_out = sizeof(lzfile->buf);
205           lzfile->strm.next_out = lzfile->buf;
206           ret = lzma_code(&lzfile->strm, LZMA_FINISH);
207           if (ret != LZMA_OK && ret != LZMA_STREAM_END)
208             return -1;
209           n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
210           if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
211             return -1;
212           if (ret == LZMA_STREAM_END)
213             break;
214         }
215     }
216   lzma_end(&lzfile->strm);
217   rc = fclose(lzfile->file);
218   free(lzfile);
219   return rc;
220 }
221
222 static ssize_t lzread(void *cookie, char *buf, size_t len)
223 {
224   LZFILE *lzfile = cookie;
225   lzma_ret ret;
226   int eof = 0;
227
228   if (!lzfile || lzfile->encoding)
229     return -1;
230   if (lzfile->eof)
231     return 0;
232   lzfile->strm.next_out = (unsigned char *)buf;
233   lzfile->strm.avail_out = len;
234   for (;;)
235     {
236       if (!lzfile->strm.avail_in)
237         {
238           lzfile->strm.next_in = lzfile->buf;
239           lzfile->strm.avail_in = fread(lzfile->buf, 1, sizeof(lzfile->buf), lzfile->file);
240           if (!lzfile->strm.avail_in)
241             eof = 1;
242         }
243       ret = lzma_code(&lzfile->strm, LZMA_RUN);
244       if (ret == LZMA_STREAM_END)
245         {
246           lzfile->eof = 1;
247           return len - lzfile->strm.avail_out;
248         }
249       if (ret != LZMA_OK)
250         return -1;
251       if (!lzfile->strm.avail_out)
252         return len;
253       if (eof)
254         return -1;
255     }
256 }
257
258 static ssize_t lzwrite(void *cookie, const char *buf, size_t len)
259 {
260   LZFILE *lzfile = cookie;
261   lzma_ret ret;
262   size_t n;
263   if (!lzfile || !lzfile->encoding)
264     return -1;
265   if (!len)
266     return 0;
267   lzfile->strm.next_in = (unsigned char *)buf;
268   lzfile->strm.avail_in = len;
269   for (;;)
270     {
271       lzfile->strm.next_out = lzfile->buf;
272       lzfile->strm.avail_out = sizeof(lzfile->buf);
273       ret = lzma_code(&lzfile->strm, LZMA_RUN);
274       if (ret != LZMA_OK)
275         return -1;
276       n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
277       if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
278         return -1;
279       if (!lzfile->strm.avail_in)
280         return len;
281     }
282 }
283
284 static inline FILE *myxzfopen(const char *fn, const char *mode)
285 {
286   LZFILE *lzf = lzopen(fn, mode, -1, 1);
287   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
288 }
289
290 static inline FILE *myxzfdopen(int fd, const char *mode)
291 {
292   LZFILE *lzf = lzopen(0, mode, fd, 1);
293   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
294 }
295
296 static inline FILE *mylzfopen(const char *fn, const char *mode)
297 {
298   LZFILE *lzf = lzopen(fn, mode, -1, 0);
299   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
300 }
301
302 static inline FILE *mylzfdopen(int fd, const char *mode)
303 {
304   LZFILE *lzf = lzopen(0, mode, fd, 0);
305   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
306 }
307
308 #endif /* ENABLE_LZMA_COMPRESSION */
309
310
311 FILE *
312 solv_xfopen(const char *fn, const char *mode)
313 {
314   char *suf;
315
316   if (!fn)
317     return 0;
318   if (!mode)
319     mode = "r";
320   suf = strrchr(fn, '.');
321   if (suf && !strcmp(suf, ".gz"))
322     return mygzfopen(fn, mode);
323 #ifdef ENABLE_LZMA_COMPRESSION
324   if (suf && !strcmp(suf, ".xz"))
325     return myxzfopen(fn, mode);
326   if (suf && !strcmp(suf, ".lzma"))
327     return mylzfopen(fn, mode);
328 #else
329   if (suf && !strcmp(suf, ".xz"))
330     return 0;
331   if (suf && !strcmp(suf, ".lzma"))
332     return 0;
333 #endif
334 #ifdef ENABLE_BZIP2_COMPRESSION
335   if (suf && !strcmp(suf, ".bz2"))
336     return mybzfopen(fn, mode);
337 #else
338   if (suf && !strcmp(suf, ".bz2"))
339     return 0;
340 #endif
341   return fopen(fn, mode);
342 }
343
344 FILE *
345 solv_xfopen_fd(const char *fn, int fd, const char *mode)
346 {
347   const char *simplemode = mode;
348   char *suf;
349
350   suf = fn ? strrchr(fn, '.') : 0;
351   if (!mode)
352     {
353       int fl = fcntl(fd, F_GETFL, 0);
354       if (fl == -1)
355         return 0;
356       fl &= O_RDONLY|O_WRONLY|O_RDWR;
357       if (fl == O_WRONLY)
358         mode = simplemode = "w";
359       else if (fl == O_RDWR)
360         {
361           mode = "r+";
362           simplemode = "r";
363         }
364       else
365         mode = simplemode = "r";
366     }
367   if (suf && !strcmp(suf, ".gz"))
368     return mygzfdopen(fd, simplemode);
369 #ifdef ENABLE_LZMA_COMPRESSION
370   if (suf && !strcmp(suf, ".xz"))
371     return myxzfdopen(fd, simplemode);
372   if (suf && !strcmp(suf, ".lzma"))
373     return mylzfdopen(fd, simplemode);
374 #else
375   if (suf && !strcmp(suf, ".xz"))
376     return 0;
377   if (suf && !strcmp(suf, ".lzma"))
378     return 0;
379 #endif
380 #ifdef ENABLE_BZIP2_COMPRESSION
381   if (suf && !strcmp(suf, ".bz2"))
382     return mybzfdopen(fd, simplemode);
383 #else
384   if (suf && !strcmp(suf, ".bz2"))
385     return 0;
386 #endif
387   return fdopen(fd, mode);
388 }
389
390 int
391 solv_xfopen_iscompressed(const char *fn)
392 {
393   const char *suf = fn ? strrchr(fn, '.') : 0;
394   if (!suf)
395     return 0;
396   if (!strcmp(suf, ".gz"))
397     return 1;
398   if (!strcmp(suf, ".xz") || !strcmp(suf, ".lzma"))
399 #ifdef ENABLE_LZMA_COMPRESSION
400     return 1;
401 #else
402     return -1;
403 #endif
404   if (!strcmp(suf, ".bz2"))
405 #ifdef ENABLE_BZIP2_COMPRESSION
406     return 1;
407 #else
408     return -1;
409 #endif
410   return 0;
411 }
412
413 struct bufcookie {
414   char **bufp;
415   size_t *buflp;
416   char *freemem;
417   size_t bufl_int;
418 };
419
420 static ssize_t cookie_bufread(void *cookie, char *buf, size_t nbytes)
421 {
422   struct bufcookie *bc = cookie;
423   size_t n = *bc->buflp > nbytes ? nbytes : *bc->buflp;
424   if (n)
425     {
426       memcpy(buf, *bc->bufp, n);
427       *bc->bufp += n;
428       *bc->buflp -= n;
429     }
430   return n;
431 }
432
433 static ssize_t cookie_bufwrite(void *cookie, const char *buf, size_t nbytes)
434 {
435   struct bufcookie *bc = cookie;
436   int n = nbytes > 0x40000000 ? 0x40000000 : nbytes;
437   if (n)
438     {
439       *bc->bufp = solv_extend(*bc->bufp, *bc->buflp, n + 1, 1, 4095);
440       memcpy(*bc->bufp, buf, n);
441       (*bc->bufp)[n] = 0;       /* zero-terminate */
442       *bc->buflp += n;
443     }
444   return n;
445 }
446
447 static int cookie_bufclose(void *cookie)
448 {
449   struct bufcookie *bc = cookie;
450   if (bc->freemem)
451     solv_free(bc->freemem);
452   solv_free(bc);
453   return 0;
454 }
455
456 FILE *
457 solv_xfopen_buf(const char *fn, char **bufp, size_t *buflp, const char *mode)
458 {
459   struct bufcookie *bc;
460   FILE *fp;
461   if (*mode != 'r' && *mode != 'w')
462     return 0;
463   bc = solv_calloc(1, sizeof(*bc));
464   bc->freemem = 0;
465   bc->bufp = bufp;
466   if (!buflp)
467     {
468       bc->bufl_int = *mode == 'w' ? 0 : strlen(*bufp);
469       buflp = &bc->bufl_int;
470     }
471   bc->buflp = buflp;
472   if (*mode == 'w')
473     {
474       *bc->bufp = solv_extend(0, 0, 1, 1, 4095);        /* always zero-terminate */
475       (*bc->bufp)[0] = 0;
476       *bc->buflp = 0;
477     }
478   fp = cookieopen(bc, mode, cookie_bufread, cookie_bufwrite, cookie_bufclose);
479   if (!strcmp(mode, "rf"))      /* auto-free */
480     bc->freemem = *bufp;
481   if (!fp)
482     {
483       if (*mode == 'w')
484         *bc->bufp = solv_free(*bc->bufp);
485       cookie_bufclose(bc);
486     }
487   return fp;
488 }