- fix gzFile usage, oops
[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
79 #ifdef ENABLE_LZMA_COMPRESSION
80
81 #include <lzma.h>
82
83 /* lzma code written by me in 2008 for rpm's rpmio.c */
84
85 typedef struct lzfile {
86   unsigned char buf[1 << 15];
87   lzma_stream strm;
88   FILE *file;
89   int encoding;
90   int eof;
91 } LZFILE;
92
93 static inline lzma_ret setup_alone_encoder(lzma_stream *strm, int level)
94 {
95   lzma_options_lzma options;
96   lzma_lzma_preset(&options, level);
97   return lzma_alone_encoder(strm, &options);
98 }
99
100 static lzma_stream stream_init = LZMA_STREAM_INIT;
101
102 static LZFILE *lzopen(const char *path, const char *mode, int fd, int isxz)
103 {
104   int level = 7;
105   int encoding = 0;
106   FILE *fp;
107   LZFILE *lzfile;
108   lzma_ret ret;
109
110   if (!path && fd < 0)
111     return 0;
112   for (; *mode; mode++)
113     {
114       if (*mode == 'w')
115         encoding = 1;
116       else if (*mode == 'r')
117         encoding = 0;
118       else if (*mode >= '1' && *mode <= '9')
119         level = *mode - '0';
120     }
121   if (fd != -1)
122     fp = fdopen(fd, encoding ? "w" : "r");
123   else
124     fp = fopen(path, encoding ? "w" : "r");
125   if (!fp)
126     return 0;
127   lzfile = calloc(1, sizeof(*lzfile));
128   if (!lzfile)
129     {
130       fclose(fp);
131       return 0;
132     }
133   lzfile->file = fp;
134   lzfile->encoding = encoding;
135   lzfile->eof = 0;
136   lzfile->strm = stream_init;
137   if (encoding)
138     {
139       if (isxz)
140         ret = lzma_easy_encoder(&lzfile->strm, level, LZMA_CHECK_SHA256);
141       else
142         ret = setup_alone_encoder(&lzfile->strm, level);
143     }
144   else
145     ret = lzma_auto_decoder(&lzfile->strm, 100 << 20, 0);
146   if (ret != LZMA_OK)
147     {
148       fclose(fp);
149       free(lzfile);
150       return 0;
151     }
152   return lzfile;
153 }
154
155 static int lzclose(void *cookie)
156 {
157   LZFILE *lzfile = cookie;
158   lzma_ret ret;
159   size_t n;
160   int rc;
161
162   if (!lzfile)
163     return -1;
164   if (lzfile->encoding)
165     {
166       for (;;)
167         {
168           lzfile->strm.avail_out = sizeof(lzfile->buf);
169           lzfile->strm.next_out = lzfile->buf;
170           ret = lzma_code(&lzfile->strm, LZMA_FINISH);
171           if (ret != LZMA_OK && ret != LZMA_STREAM_END)
172             return -1;
173           n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
174           if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
175             return -1;
176           if (ret == LZMA_STREAM_END)
177             break;
178         }
179     }
180   lzma_end(&lzfile->strm);
181   rc = fclose(lzfile->file);
182   free(lzfile);
183   return rc;
184 }
185
186 static ssize_t lzread(void *cookie, char *buf, size_t len)
187 {
188   LZFILE *lzfile = cookie;
189   lzma_ret ret;
190   int eof = 0;
191
192   if (!lzfile || lzfile->encoding)
193     return -1;
194   if (lzfile->eof)
195     return 0;
196   lzfile->strm.next_out = (unsigned char *)buf;
197   lzfile->strm.avail_out = len;
198   for (;;)
199     {
200       if (!lzfile->strm.avail_in)
201         {
202           lzfile->strm.next_in = lzfile->buf;
203           lzfile->strm.avail_in = fread(lzfile->buf, 1, sizeof(lzfile->buf), lzfile->file);
204           if (!lzfile->strm.avail_in)
205             eof = 1;
206         }
207       ret = lzma_code(&lzfile->strm, LZMA_RUN);
208       if (ret == LZMA_STREAM_END)
209         {
210           lzfile->eof = 1;
211           return len - lzfile->strm.avail_out;
212         }
213       if (ret != LZMA_OK)
214         return -1;
215       if (!lzfile->strm.avail_out)
216         return len;
217       if (eof)
218         return -1;
219     }
220 }
221
222 static ssize_t lzwrite(void *cookie, const char *buf, size_t len)
223 {
224   LZFILE *lzfile = cookie;
225   lzma_ret ret;
226   size_t n;
227   if (!lzfile || !lzfile->encoding)
228     return -1;
229   if (!len)
230     return 0;
231   lzfile->strm.next_in = (unsigned char *)buf;
232   lzfile->strm.avail_in = len;
233   for (;;)
234     {
235       lzfile->strm.next_out = lzfile->buf;
236       lzfile->strm.avail_out = sizeof(lzfile->buf);
237       ret = lzma_code(&lzfile->strm, LZMA_RUN);
238       if (ret != LZMA_OK)
239         return -1;
240       n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
241       if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
242         return -1;
243       if (!lzfile->strm.avail_in)
244         return len;
245     }
246 }
247
248 static inline FILE *myxzfopen(const char *fn, const char *mode)
249 {
250   LZFILE *lzf = lzopen(fn, mode, -1, 1);
251   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
252 }
253
254 static inline FILE *myxzfdopen(int fd, const char *mode)
255 {
256   LZFILE *lzf = lzopen(0, mode, fd, 1);
257   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
258 }
259
260 static inline FILE *mylzfopen(const char *fn, const char *mode)
261 {
262   LZFILE *lzf = lzopen(fn, mode, -1, 0);
263   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
264 }
265
266 static inline FILE *mylzfdopen(int fd, const char *mode)
267 {
268   LZFILE *lzf = lzopen(0, mode, fd, 0);
269   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
270 }
271
272 #endif /* ENABLE_LZMA_COMPRESSION */
273
274
275 FILE *
276 solv_xfopen(const char *fn, const char *mode)
277 {
278   char *suf;
279
280   if (!fn)
281     return 0;
282   if (!mode)
283     mode = "r";
284   suf = strrchr(fn, '.');
285   if (suf && !strcmp(suf, ".gz"))
286     return mygzfopen(fn, mode);
287 #ifdef ENABLE_LZMA_COMPRESSION
288   if (suf && !strcmp(suf, ".xz"))
289     return myxzfopen(fn, mode);
290   if (suf && !strcmp(suf, ".lzma"))
291     return mylzfopen(fn, mode);
292 #endif
293   return fopen(fn, mode);
294 }
295
296 FILE *
297 solv_xfopen_fd(const char *fn, int fd, const char *mode)
298 {
299   const char *simplemode = mode;
300   char *suf;
301
302   suf = fn ? strrchr(fn, '.') : 0;
303   if (!mode)
304     {
305       int fl = fcntl(fd, F_GETFL, 0);
306       if (fl == -1)
307         return 0;
308       fl &= O_RDONLY|O_WRONLY|O_RDWR;
309       if (fl == O_WRONLY)
310         mode = simplemode = "w";
311       else if (fl == O_RDWR)
312         {
313           mode = "r+";
314           simplemode = "r";
315         }
316       else
317         mode = simplemode = "r";
318     }
319   if (suf && !strcmp(suf, ".gz"))
320     return mygzfdopen(fd, simplemode);
321 #ifdef ENABLE_LZMA_COMPRESSION
322   if (suf && !strcmp(suf, ".xz"))
323     return myxzfdopen(fd, simplemode);
324   if (suf && !strcmp(suf, ".lzma"))
325     return mylzfdopen(fd, simplemode);
326 #endif
327   return fdopen(fd, mode);
328 }
329
330 struct bufcookie {
331   char **bufp;
332   size_t *buflp;
333   char *freemem;
334   size_t bufl_int;
335 };
336
337 static ssize_t cookie_bufread(void *cookie, char *buf, size_t nbytes)
338 {
339   struct bufcookie *bc = cookie;
340   size_t n = *bc->buflp > nbytes ? nbytes : *bc->buflp;
341   if (n)
342     {
343       memcpy(buf, *bc->bufp, n);
344       *bc->bufp += n;
345       *bc->buflp -= n;
346     }
347   return n;
348 }
349
350 static ssize_t cookie_bufwrite(void *cookie, const char *buf, size_t nbytes)
351 {
352   struct bufcookie *bc = cookie;
353   int n = nbytes > 0x40000000 ? 0x40000000 : nbytes;
354   if (n)
355     {
356       *bc->bufp = solv_extend(*bc->bufp, *bc->buflp, n + 1, 1, 4095);
357       memcpy(*bc->bufp, buf, n);
358       (*bc->bufp)[n] = 0;       /* zero-terminate */
359       *bc->buflp += n;
360     }
361   return n;
362 }
363
364 static int cookie_bufclose(void *cookie)
365 {
366   struct bufcookie *bc = cookie;
367   if (bc->freemem)
368     solv_free(bc->freemem);
369   solv_free(bc);
370   return 0;
371 }
372
373 FILE *
374 solv_xfopen_buf(const char *fn, char **bufp, size_t *buflp, const char *mode)
375 {
376   struct bufcookie *bc;
377   FILE *fp;
378   if (*mode != 'r' && *mode != 'w')
379     return 0;
380   bc = solv_calloc(1, sizeof(*bc));
381   bc->freemem = 0;
382   bc->bufp = bufp;
383   if (!buflp)
384     {
385       bc->bufl_int = *mode == 'w' ? 0 : strlen(*bufp);
386       buflp = &bc->bufl_int;
387     }
388   bc->buflp = buflp;
389   if (*mode == 'w')
390     {
391       *bc->bufp = solv_extend(0, 0, 1, 1, 4095);        /* always zero-terminate */
392       (*bc->bufp)[0] = 0;
393       *bc->buflp = 0;
394     }
395   fp = cookieopen(bc, mode, cookie_bufread, cookie_bufwrite, cookie_bufclose);
396   if (!strcmp(mode, "rf"))      /* auto-free */
397     bc->freemem = *bufp;
398   if (!fp)
399     {
400       *bc->bufp = solv_free(*bc->bufp);
401       *bc->buflp = 0;
402       cookie_bufclose(bc);
403     }
404   return fp;
405 }