343aed86834afb78f073118a88ab96c417109e29
[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 <fcntl.h>
14
15 #include "solv_xfopen.h"
16 #include "util.h"
17
18
19 static FILE *cookieopen(void *cookie, const char *mode,
20         ssize_t (*cread)(void *, char *, size_t),
21         ssize_t (*cwrite)(void *, const char *, size_t),
22         int (*cclose)(void *))
23 {
24 #ifdef HAVE_FUNOPEN
25   if (!cookie)
26     return 0;
27   return funopen(cookie,
28       (int (*)(void *, char *, int))(*mode == 'r' ? cread : NULL),              /* readfn */
29       (int (*)(void *, const char *, int))(*mode == 'w' ? cwrite : NULL),       /* writefn */
30       (fpos_t (*)(void *, fpos_t, int))NULL,                                    /* seekfn */
31       cclose
32       );
33 #elif defined(HAVE_FOPENCOOKIE)
34   cookie_io_functions_t cio;
35
36   if (!cookie)
37     return 0;
38   memset(&cio, 0, sizeof(cio));
39   if (*mode == 'r')
40     cio.read = cread;
41   else if (*mode == 'w')
42     cio.write = cwrite;
43   cio.close = cclose;
44   return  fopencookie(cookie, *mode == 'w' ? "w" : "r", cio);
45 #else
46 # error Need to implement custom I/O
47 #endif
48 }
49
50
51 #ifdef ENABLE_ZLIB_COMPRESSION
52
53 /* gzip compression */
54
55 #include <zlib.h>
56
57 static ssize_t cookie_gzread(void *cookie, char *buf, size_t nbytes)
58 {
59   return gzread((gzFile)cookie, buf, nbytes);
60 }
61
62 static ssize_t cookie_gzwrite(void *cookie, const char *buf, size_t nbytes)
63 {
64   return gzwrite((gzFile)cookie, buf, nbytes);
65 }
66
67 static int cookie_gzclose(void *cookie)
68 {
69   return gzclose((gzFile)cookie);
70 }
71
72 static inline FILE *mygzfopen(const char *fn, const char *mode)
73 {
74   gzFile gzf = gzopen(fn, mode);
75   return cookieopen(gzf, mode, cookie_gzread, cookie_gzwrite, cookie_gzclose);
76 }
77
78 static inline FILE *mygzfdopen(int fd, const char *mode)
79 {
80   gzFile gzf = gzdopen(fd, mode);
81   return cookieopen(gzf, mode, cookie_gzread, cookie_gzwrite, cookie_gzclose);
82 }
83
84 #endif
85
86
87 #ifdef ENABLE_BZIP2_COMPRESSION
88
89 /* bzip2 compression */
90
91 #include <bzlib.h>
92
93 static ssize_t cookie_bzread(void *cookie, char *buf, size_t nbytes)
94 {
95   return BZ2_bzread((BZFILE *)cookie, buf, nbytes);
96 }
97
98 static ssize_t cookie_bzwrite(void *cookie, const char *buf, size_t nbytes)
99 {
100   return BZ2_bzwrite((BZFILE *)cookie, (char *)buf, nbytes);
101 }
102
103 static int cookie_bzclose(void *cookie)
104 {
105   BZ2_bzclose((BZFILE *)cookie);
106   return 0;
107 }
108
109 static inline FILE *mybzfopen(const char *fn, const char *mode)
110 {
111   BZFILE *bzf = BZ2_bzopen(fn, mode);
112   return cookieopen(bzf, mode, cookie_bzread, cookie_bzwrite, cookie_bzclose);
113 }
114
115 static inline FILE *mybzfdopen(int fd, const char *mode)
116 {
117   BZFILE *bzf = BZ2_bzdopen(fd, mode);
118   return cookieopen(bzf, mode, cookie_bzread, cookie_bzwrite, cookie_bzclose);
119 }
120
121 #endif
122
123
124 #ifdef ENABLE_LZMA_COMPRESSION
125
126 /* lzma code written by me in 2008 for rpm's rpmio.c */
127
128 #include <lzma.h>
129
130 typedef struct lzfile {
131   unsigned char buf[1 << 15];
132   lzma_stream strm;
133   FILE *file;
134   int encoding;
135   int eof;
136 } LZFILE;
137
138 static inline lzma_ret setup_alone_encoder(lzma_stream *strm, int level)
139 {
140   lzma_options_lzma options;
141   lzma_lzma_preset(&options, level);
142   return lzma_alone_encoder(strm, &options);
143 }
144
145 static lzma_stream stream_init = LZMA_STREAM_INIT;
146
147 static LZFILE *lzopen(const char *path, const char *mode, int fd, int isxz)
148 {
149   int level = 7;
150   int encoding = 0;
151   FILE *fp;
152   LZFILE *lzfile;
153   lzma_ret ret;
154
155   if (!path && fd < 0)
156     return 0;
157   for (; *mode; mode++)
158     {
159       if (*mode == 'w')
160         encoding = 1;
161       else if (*mode == 'r')
162         encoding = 0;
163       else if (*mode >= '1' && *mode <= '9')
164         level = *mode - '0';
165     }
166   if (fd != -1)
167     fp = fdopen(fd, encoding ? "w" : "r");
168   else
169     fp = fopen(path, encoding ? "w" : "r");
170   if (!fp)
171     return 0;
172   lzfile = calloc(1, sizeof(*lzfile));
173   if (!lzfile)
174     {
175       fclose(fp);
176       return 0;
177     }
178   lzfile->file = fp;
179   lzfile->encoding = encoding;
180   lzfile->eof = 0;
181   lzfile->strm = stream_init;
182   if (encoding)
183     {
184       if (isxz)
185         ret = lzma_easy_encoder(&lzfile->strm, level, LZMA_CHECK_SHA256);
186       else
187         ret = setup_alone_encoder(&lzfile->strm, level);
188     }
189   else
190     ret = lzma_auto_decoder(&lzfile->strm, 100 << 20, 0);
191   if (ret != LZMA_OK)
192     {
193       fclose(fp);
194       free(lzfile);
195       return 0;
196     }
197   return lzfile;
198 }
199
200 static int lzclose(void *cookie)
201 {
202   LZFILE *lzfile = cookie;
203   lzma_ret ret;
204   size_t n;
205   int rc;
206
207   if (!lzfile)
208     return -1;
209   if (lzfile->encoding)
210     {
211       for (;;)
212         {
213           lzfile->strm.avail_out = sizeof(lzfile->buf);
214           lzfile->strm.next_out = lzfile->buf;
215           ret = lzma_code(&lzfile->strm, LZMA_FINISH);
216           if (ret != LZMA_OK && ret != LZMA_STREAM_END)
217             return -1;
218           n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
219           if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
220             return -1;
221           if (ret == LZMA_STREAM_END)
222             break;
223         }
224     }
225   lzma_end(&lzfile->strm);
226   rc = fclose(lzfile->file);
227   free(lzfile);
228   return rc;
229 }
230
231 static ssize_t lzread(void *cookie, char *buf, size_t len)
232 {
233   LZFILE *lzfile = cookie;
234   lzma_ret ret;
235   int eof = 0;
236
237   if (!lzfile || lzfile->encoding)
238     return -1;
239   if (lzfile->eof)
240     return 0;
241   lzfile->strm.next_out = (unsigned char *)buf;
242   lzfile->strm.avail_out = len;
243   for (;;)
244     {
245       if (!lzfile->strm.avail_in)
246         {
247           lzfile->strm.next_in = lzfile->buf;
248           lzfile->strm.avail_in = fread(lzfile->buf, 1, sizeof(lzfile->buf), lzfile->file);
249           if (!lzfile->strm.avail_in)
250             eof = 1;
251         }
252       ret = lzma_code(&lzfile->strm, LZMA_RUN);
253       if (ret == LZMA_STREAM_END)
254         {
255           lzfile->eof = 1;
256           return len - lzfile->strm.avail_out;
257         }
258       if (ret != LZMA_OK)
259         return -1;
260       if (!lzfile->strm.avail_out)
261         return len;
262       if (eof)
263         return -1;
264     }
265 }
266
267 static ssize_t lzwrite(void *cookie, const char *buf, size_t len)
268 {
269   LZFILE *lzfile = cookie;
270   lzma_ret ret;
271   size_t n;
272   if (!lzfile || !lzfile->encoding)
273     return -1;
274   if (!len)
275     return 0;
276   lzfile->strm.next_in = (unsigned char *)buf;
277   lzfile->strm.avail_in = len;
278   for (;;)
279     {
280       lzfile->strm.next_out = lzfile->buf;
281       lzfile->strm.avail_out = sizeof(lzfile->buf);
282       ret = lzma_code(&lzfile->strm, LZMA_RUN);
283       if (ret != LZMA_OK)
284         return -1;
285       n = sizeof(lzfile->buf) - lzfile->strm.avail_out;
286       if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n)
287         return -1;
288       if (!lzfile->strm.avail_in)
289         return len;
290     }
291 }
292
293 static inline FILE *myxzfopen(const char *fn, const char *mode)
294 {
295   LZFILE *lzf = lzopen(fn, mode, -1, 1);
296   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
297 }
298
299 static inline FILE *myxzfdopen(int fd, const char *mode)
300 {
301   LZFILE *lzf = lzopen(0, mode, fd, 1);
302   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
303 }
304
305 static inline FILE *mylzfopen(const char *fn, const char *mode)
306 {
307   LZFILE *lzf = lzopen(fn, mode, -1, 0);
308   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
309 }
310
311 static inline FILE *mylzfdopen(int fd, const char *mode)
312 {
313   LZFILE *lzf = lzopen(0, mode, fd, 0);
314   return cookieopen(lzf, mode, lzread, lzwrite, lzclose);
315 }
316
317 #endif /* ENABLE_LZMA_COMPRESSION */
318
319 #ifdef ENABLE_ZSTD_COMPRESSION
320
321 #include <zstd.h>
322
323 typedef struct zstdfile {
324   ZSTD_CStream *cstream;
325   ZSTD_DStream *dstream;
326   FILE *file;
327   int encoding;
328   int eof;
329   ZSTD_inBuffer in;
330   ZSTD_outBuffer out;
331   unsigned char buf[1 << 15];
332 } ZSTDFILE;
333
334 static ZSTDFILE *zstdopen(const char *path, const char *mode, int fd)
335 {
336   int level = 7;
337   int encoding = 0;
338   FILE *fp;
339   ZSTDFILE *zstdfile;
340
341   if (!path && fd < 0)
342     return 0;
343   for (; *mode; mode++)
344     {
345       if (*mode == 'w')
346         encoding = 1;
347       else if (*mode == 'r')
348         encoding = 0;
349       else if (*mode >= '1' && *mode <= '9')
350         level = *mode - '0';
351     }
352   if (fd != -1)
353     fp = fdopen(fd, encoding ? "w" : "r");
354   else
355     fp = fopen(path, encoding ? "w" : "r");
356   if (!fp)
357     return 0;
358   zstdfile = solv_calloc(1, sizeof(*zstdfile));
359   zstdfile->encoding = encoding;
360   if (encoding)
361     {
362       zstdfile->cstream = ZSTD_createCStream();
363       zstdfile->encoding = 1;
364       if (!zstdfile->cstream)
365         {
366           solv_free(zstdfile);
367           fclose(fp);
368           return 0;
369         }
370       if (ZSTD_isError(ZSTD_initCStream(zstdfile->cstream, level)))
371         {
372           ZSTD_freeCStream(zstdfile->cstream);
373           solv_free(zstdfile);
374           fclose(fp);
375           return 0;
376         }
377       zstdfile->out.dst = zstdfile->buf;
378       zstdfile->out.pos = 0;
379       zstdfile->out.size = sizeof(zstdfile->buf);
380     }
381   else
382     {
383       zstdfile->dstream = ZSTD_createDStream();
384       if (ZSTD_isError(ZSTD_initDStream(zstdfile->dstream)))
385         {
386           ZSTD_freeDStream(zstdfile->dstream);
387           solv_free(zstdfile);
388           fclose(fp);
389           return 0;
390         }
391       zstdfile->in.src = zstdfile->buf;
392       zstdfile->in.pos = 0;
393       zstdfile->in.size = 0;
394     }
395   zstdfile->file = fp;
396   return zstdfile;
397 }
398
399 static int zstdclose(void *cookie)
400 {
401   ZSTDFILE *zstdfile = cookie;
402   int rc;
403
404   if (!zstdfile)
405     return -1;
406   if (zstdfile->encoding)
407     {
408       for (;;)
409         {
410           size_t ret;
411           zstdfile->out.pos = 0;
412           ret = ZSTD_endStream(zstdfile->cstream, &zstdfile->out);
413           if (ZSTD_isError(ret))
414             return -1;
415           if (zstdfile->out.pos && fwrite(zstdfile->buf, 1, zstdfile->out.pos, zstdfile->file) != zstdfile->out.pos)
416             return -1;
417           if (ret == 0)
418             break;
419         }
420       ZSTD_freeCStream(zstdfile->cstream);
421     }
422   else
423     {
424       ZSTD_freeDStream(zstdfile->dstream);
425     }
426   rc = fclose(zstdfile->file);
427   free(zstdfile);
428   return rc;
429 }
430
431 static ssize_t zstdread(void *cookie, char *buf, size_t len)
432 {
433   ZSTDFILE *zstdfile = cookie;
434   int eof = 0;
435   size_t ret = 0;
436   if (!zstdfile || zstdfile->encoding)
437     return -1;
438   if (zstdfile->eof)
439     return 0;
440   zstdfile->out.dst = buf;
441   zstdfile->out.pos = 0;
442   zstdfile->out.size = len;
443   for (;;)
444     {
445       if (!eof && zstdfile->in.pos == zstdfile->in.size)
446         {
447           zstdfile->in.pos = 0;
448           zstdfile->in.size = fread(zstdfile->buf, 1, sizeof(zstdfile->buf), zstdfile->file);
449           if (!zstdfile->in.size)
450             eof = 1;
451         }
452       if (ret || !eof)
453         ret = ZSTD_decompressStream(zstdfile->dstream, &zstdfile->out, &zstdfile->in);
454       if (ret == 0 && eof)
455         {
456           zstdfile->eof = 1;
457           return zstdfile->out.pos;
458         }
459       if (ZSTD_isError(ret))
460         return -1;
461       if (zstdfile->out.pos == len)
462         return len;
463     }
464 }
465
466 static ssize_t zstdwrite(void *cookie, const char *buf, size_t len)
467 {
468   ZSTDFILE *zstdfile = cookie;
469   if (!zstdfile || !zstdfile->encoding)
470     return -1;
471   if (!len)
472     return 0;
473   zstdfile->in.src = buf;
474   zstdfile->in.pos = 0;
475   zstdfile->in.size = len;
476
477   for (;;)
478     {
479       size_t ret;
480       zstdfile->out.pos = 0;
481       ret = ZSTD_compressStream(zstdfile->cstream, &zstdfile->out, &zstdfile->in);
482       if (ZSTD_isError(ret))
483         return -1;
484       if (zstdfile->out.pos && fwrite(zstdfile->buf, 1, zstdfile->out.pos, zstdfile->file) != zstdfile->out.pos)
485         return -1;
486       if (zstdfile->in.pos == len)
487         return len;
488     }
489 }
490
491 static inline FILE *myzstdfopen(const char *fn, const char *mode)
492 {
493   ZSTDFILE *zstdfile = zstdopen(fn, mode, -1);
494   return cookieopen(zstdfile, mode, zstdread, zstdwrite, zstdclose);
495 }
496
497 static inline FILE *myzstdfdopen(int fd, const char *mode)
498 {
499   ZSTDFILE *zstdfile = zstdopen(0, mode, fd);
500   return cookieopen(zstdfile, mode, zstdread, zstdwrite, zstdclose);
501 }
502
503 #endif
504
505 #ifdef ENABLE_ZCHUNK_COMPRESSION
506
507 #ifdef WITH_SYSTEM_ZCHUNK
508 /* use the system's zchunk library that supports reading and writing of zchunk files */
509
510 #include <zck.h>
511
512 static ssize_t cookie_zckread(void *cookie, char *buf, size_t nbytes)
513 {
514   return zck_read((zckCtx *)cookie, buf, nbytes);
515 }
516
517 static ssize_t cookie_zckwrite(void *cookie, const char *buf, size_t nbytes)
518 {
519   return zck_write((zckCtx *)cookie, buf, nbytes);
520 }
521
522 static int cookie_zckclose(void *cookie)
523 {
524   zckCtx *zck = (zckCtx *)cookie;
525   int fd = zck_get_fd(zck);
526   if (fd != -1)
527     close(fd);
528   zck_free(&zck);
529   return 0;
530 }
531
532 static void *zchunkopen(const char *path, const char *mode, int fd)
533 {
534   zckCtx *f;
535
536   if (!path && fd < 0)
537     return 0;
538   if (fd == -1)
539     {
540       if (*mode != 'w')
541         fd = open(path, O_RDONLY);
542       else
543         fd = open(path, O_WRONLY | O_CREAT, 0666);
544       if (fd == -1)
545         return 0;
546     }
547   f = zck_create();
548   if (!f)
549     {
550       close(fd);
551       return 0;
552     }
553   if (*mode != 'w')
554     {
555       if(!zck_init_read(f, fd))
556         return 0;
557     }
558    else
559     {
560       if(!zck_init_write(f, fd))
561         return 0;
562     }
563   return cookieopen(f, mode, cookie_zckread, cookie_zckwrite, cookie_zckclose);
564 }
565
566 #else
567
568 #include "solv_zchunk.h"
569 /* use the libsolv's limited zchunk implementation that only supports reading of zchunk files */
570
571 static void *zchunkopen(const char *path, const char *mode, int fd)
572 {
573   FILE *fp;
574   void *f;
575   if (!path && fd < 0)
576     return 0;
577   if (fd != -1)
578     fp = fdopen(fd, mode);
579   else
580     fp = fopen(path, mode);
581   if (!fp)
582     return 0;
583   if (strcmp(mode, "r") != 0)
584     return 0;
585   f = solv_zchunk_open(fp, 1);
586   if (!f)
587     fclose(fp);
588   return cookieopen(f, mode, (ssize_t (*)(void *, char *, size_t))solv_zchunk_read, 0, (int (*)(void *))solv_zchunk_close);
589 }
590
591 #endif
592
593 static inline FILE *myzchunkfopen(const char *fn, const char *mode)
594 {
595   return zchunkopen(fn, mode, -1);
596 }
597
598 static inline FILE *myzchunkfdopen(int fd, const char *mode)
599 {
600   return zchunkopen(0, mode, fd);
601 }
602
603 #endif /* ENABLE_ZCHUNK_COMPRESSION */
604
605
606 FILE *
607 solv_xfopen(const char *fn, const char *mode)
608 {
609   char *suf;
610
611   if (!fn)
612     return 0;
613   if (!mode)
614     mode = "r";
615   suf = strrchr(fn, '.');
616 #ifdef ENABLE_ZLIB_COMPRESSION
617   if (suf && !strcmp(suf, ".gz"))
618     return mygzfopen(fn, mode);
619 #else
620   if (suf && !strcmp(suf, ".gz"))
621     return 0;
622 #endif
623 #ifdef ENABLE_LZMA_COMPRESSION
624   if (suf && !strcmp(suf, ".xz"))
625     return myxzfopen(fn, mode);
626   if (suf && !strcmp(suf, ".lzma"))
627     return mylzfopen(fn, mode);
628 #else
629   if (suf && !strcmp(suf, ".xz"))
630     return 0;
631   if (suf && !strcmp(suf, ".lzma"))
632     return 0;
633 #endif
634 #ifdef ENABLE_BZIP2_COMPRESSION
635   if (suf && !strcmp(suf, ".bz2"))
636     return mybzfopen(fn, mode);
637 #else
638   if (suf && !strcmp(suf, ".bz2"))
639     return 0;
640 #endif
641 #ifdef ENABLE_ZSTD_COMPRESSION
642   if (suf && !strcmp(suf, ".zst"))
643     return myzstdfopen(fn, mode);
644 #else
645   if (suf && !strcmp(suf, ".zst"))
646     return 0;
647 #endif
648 #ifdef ENABLE_ZCHUNK_COMPRESSION
649   if (suf && !strcmp(suf, ".zck"))
650     return myzchunkfopen(fn, mode);
651 #else
652   if (suf && !strcmp(suf, ".zck"))
653     return 0;
654 #endif
655   return fopen(fn, mode);
656 }
657
658 FILE *
659 solv_xfopen_fd(const char *fn, int fd, const char *mode)
660 {
661   const char *simplemode = mode;
662   char *suf;
663
664   suf = fn ? strrchr(fn, '.') : 0;
665   if (!mode)
666     {
667       int fl = fcntl(fd, F_GETFL, 0);
668       if (fl == -1)
669         return 0;
670       fl &= O_RDONLY|O_WRONLY|O_RDWR;
671       if (fl == O_WRONLY)
672         mode = simplemode = "w";
673       else if (fl == O_RDWR)
674         {
675           mode = "r+";
676           simplemode = "r";
677         }
678       else
679         mode = simplemode = "r";
680     }
681 #ifdef ENABLE_ZLIB_COMPRESSION
682   if (suf && !strcmp(suf, ".gz"))
683     return mygzfdopen(fd, simplemode);
684 #else
685   if (suf && !strcmp(suf, ".gz"))
686     return 0;
687 #endif
688 #ifdef ENABLE_LZMA_COMPRESSION
689   if (suf && !strcmp(suf, ".xz"))
690     return myxzfdopen(fd, simplemode);
691   if (suf && !strcmp(suf, ".lzma"))
692     return mylzfdopen(fd, simplemode);
693 #else
694   if (suf && !strcmp(suf, ".xz"))
695     return 0;
696   if (suf && !strcmp(suf, ".lzma"))
697     return 0;
698 #endif
699 #ifdef ENABLE_BZIP2_COMPRESSION
700   if (suf && !strcmp(suf, ".bz2"))
701     return mybzfdopen(fd, simplemode);
702 #else
703   if (suf && !strcmp(suf, ".bz2"))
704     return 0;
705 #endif
706 #ifdef ENABLE_ZSTD_COMPRESSION
707   if (suf && !strcmp(suf, ".zst"))
708     return myzstdfdopen(fd, simplemode);
709 #else
710   if (suf && !strcmp(suf, ".zst"))
711     return 0;
712 #endif
713 #ifdef ENABLE_ZCHUNK_COMPRESSION
714   if (suf && !strcmp(suf, ".zck"))
715     return myzchunkfdopen(fd, simplemode);
716 #else
717   if (suf && !strcmp(suf, ".zck"))
718     return 0;
719 #endif
720   return fdopen(fd, mode);
721 }
722
723 int
724 solv_xfopen_iscompressed(const char *fn)
725 {
726   const char *suf = fn ? strrchr(fn, '.') : 0;
727   if (!suf)
728     return 0;
729 #ifdef ENABLE_ZLIB_COMPRESSION
730   if (!strcmp(suf, ".gz"))
731     return 1;
732 #else
733     return -1;
734 #endif
735   if (!strcmp(suf, ".xz") || !strcmp(suf, ".lzma"))
736 #ifdef ENABLE_LZMA_COMPRESSION
737     return 1;
738 #else
739     return -1;
740 #endif
741   if (!strcmp(suf, ".bz2"))
742 #ifdef ENABLE_BZIP2_COMPRESSION
743     return 1;
744 #else
745     return -1;
746 #endif
747   if (!strcmp(suf, ".zst"))
748 #ifdef ENABLE_ZSTD_COMPRESSION
749     return 1;
750 #else
751     return -1;
752 #endif
753   if (!strcmp(suf, ".zck"))
754 #ifdef ENABLE_ZCHUNK_COMPRESSION
755     return 1;
756 #else
757     return -1;
758 #endif
759   return 0;
760 }
761
762 struct bufcookie {
763   char **bufp;
764   size_t *buflp;
765   char *freemem;
766   size_t bufl_int;
767 };
768
769 static ssize_t cookie_bufread(void *cookie, char *buf, size_t nbytes)
770 {
771   struct bufcookie *bc = cookie;
772   size_t n = *bc->buflp > nbytes ? nbytes : *bc->buflp;
773   if (n)
774     {
775       memcpy(buf, *bc->bufp, n);
776       *bc->bufp += n;
777       *bc->buflp -= n;
778     }
779   return n;
780 }
781
782 static ssize_t cookie_bufwrite(void *cookie, const char *buf, size_t nbytes)
783 {
784   struct bufcookie *bc = cookie;
785   int n = nbytes > 0x40000000 ? 0x40000000 : nbytes;
786   if (n)
787     {
788       *bc->bufp = solv_extend(*bc->bufp, *bc->buflp, n + 1, 1, 4095);
789       memcpy(*bc->bufp, buf, n);
790       (*bc->bufp)[n] = 0;       /* zero-terminate */
791       *bc->buflp += n;
792     }
793   return n;
794 }
795
796 static int cookie_bufclose(void *cookie)
797 {
798   struct bufcookie *bc = cookie;
799   if (bc->freemem)
800     solv_free(bc->freemem);
801   solv_free(bc);
802   return 0;
803 }
804
805 FILE *
806 solv_xfopen_buf(const char *fn, char **bufp, size_t *buflp, const char *mode)
807 {
808   struct bufcookie *bc;
809   FILE *fp;
810   if (*mode != 'r' && *mode != 'w')
811     return 0;
812   bc = solv_calloc(1, sizeof(*bc));
813   bc->freemem = 0;
814   bc->bufp = bufp;
815   if (!buflp)
816     {
817       bc->bufl_int = *mode == 'w' ? 0 : strlen(*bufp);
818       buflp = &bc->bufl_int;
819     }
820   bc->buflp = buflp;
821   if (*mode == 'w')
822     {
823       *bc->bufp = solv_extend(0, 0, 1, 1, 4095);        /* always zero-terminate */
824       (*bc->bufp)[0] = 0;
825       *bc->buflp = 0;
826     }
827   fp = cookieopen(bc, mode, cookie_bufread, cookie_bufwrite, cookie_bufclose);
828   if (!strcmp(mode, "rf"))      /* auto-free */
829     bc->freemem = *bufp;
830   if (!fp)
831     {
832       if (*mode == 'w')
833         *bc->bufp = solv_free(*bc->bufp);
834       cookie_bufclose(bc);
835     }
836   return fp;
837 }