07017c201e0f441006e8721f711c902829ff5af4
[platform/upstream/rpm.git] / rpmio / rpmio.c
1 #include "system.h"
2
3 #include <stdarg.h>
4
5 #ifdef  __LCLINT__
6 #define ntohl(_x)       (_x)
7 #define ntohs(_x)       (_x)
8 #define htonl(_x)       (_x)
9 #define htons(_x)       (_x)
10 typedef unsigned int            uint32_t;
11 #define INADDR_ANY              ((uint32_t) 0x00000000)
12 #define IPPROTO_IP              0
13
14 #else   /* __LCLINT__ */
15
16 #if HAVE_MACHINE_TYPES_H
17 # include <machine/types.h>
18 #endif
19
20 #include <netinet/in.h>
21 #include <arpa/inet.h>          /* XXX for inet_aton and HP-UX */
22
23 #if HAVE_NETINET_IN_SYSTM_H
24 # include <sys/types.h>
25 # include <netinet/in_systm.h>
26 #endif
27
28 #if HAVE_LIBIO_H && defined(_IO_BAD_SEEN)
29 #define _USE_LIBIO      1
30 #endif
31
32 #endif  /* __LCLINT__ */
33
34 #if HAVE_HERRNO && defined(__hpux) /* XXX HP-UX w/o -D_XOPEN_SOURCE needs */
35 extern int h_errno;
36 #endif
37
38 #ifndef IPPORT_FTP
39 #define IPPORT_FTP      21
40 #endif
41 #ifndef IPPORT_HTTP
42 #define IPPORT_HTTP     80
43 #endif
44
45
46 #if !defined(HAVE_INET_ATON)
47 static int inet_aton(const char *cp, struct in_addr *inp)
48 {
49     long addr;
50
51     addr = inet_addr(cp);
52     if (addr == ((long) -1)) return 0;
53
54     memcpy(inp, &addr, sizeof(addr));
55     return 1;
56 }
57 #endif
58
59 #if defined(USE_ALT_DNS) && USE_ALT_DNS
60 #include "dns.h"
61 #endif
62
63 #include <rpmlib.h>
64 #include <rpmio_internal.h>
65 #include "misc.h"
66
67 #define FDNREFS(fd)     (fd ? ((FD_t)fd)->nrefs : -9)
68 #define FDTO(fd)        (fd ? ((FD_t)fd)->rd_timeoutsecs : -99)
69 #define FDCPIOPOS(fd)   (fd ? ((FD_t)fd)->fd_cpioPos : -99)
70
71 #define FDONLY(fd)      assert(fdGetIo(fd) == fdio)
72 #define GZDONLY(fd)     assert(fdGetIo(fd) == gzdio)
73 #define BZDONLY(fd)     assert(fdGetIo(fd) == bzdio)
74
75 #define UFDONLY(fd)     /* assert(fdGetIo(fd) == ufdio) */
76
77 #define fdGetFILE(_fd)  ((FILE *)fdGetFp(_fd))
78
79 /*@access urlinfo@*/
80
81 #if _USE_LIBIO
82 int noLibio = 0;
83 #else
84 int noLibio = 1;
85 #endif
86
87 #define TIMEOUT_SECS 60
88 static int ftpTimeoutSecs = TIMEOUT_SECS;
89 static int httpTimeoutSecs = TIMEOUT_SECS;
90
91 int _ftp_debug = 0;
92 int _rpmio_debug = 0;
93 #define DBG(_f, _m, _x) \
94     if ((_rpmio_debug | ((_f) ? ((FD_t)(_f))->flags : 0)) & (_m)) fprintf _x
95
96 #define DBGIO(_f, _x)   DBG((_f), RPMIO_DEBUG_IO, _x)
97 #define DBGREFS(_f, _x) DBG((_f), RPMIO_DEBUG_REFS, _x)
98
99 /* =============================================================== */
100
101 static /*@observer@*/ const char * fdbg(FD_t fd)
102 {
103     static char buf[BUFSIZ];
104     char *be = buf;
105     int i;
106
107 #if DYING
108     sprintf(be, "fd %p", fd);   be += strlen(be);
109     if (fd->rd_timeoutsecs >= 0) {
110         sprintf(be, " secs %d", fd->rd_timeoutsecs);
111         be += strlen(be);
112     }
113 #endif
114     if (fd->bytesRemain != -1) {
115         sprintf(be, " clen %d", (int)fd->bytesRemain);
116         be += strlen(be);
117      }
118     if (fd->wr_chunked) {
119         strcpy(be, " chunked");
120         be += strlen(be);
121      }
122     *be++ = '\t';
123     for (i = fd->nfps; i >= 0; i--) {
124         FDSTACK_t * fps = &fd->fps[i];
125         if (i != fd->nfps)
126             *be++ = ' ';
127         *be++ = '|';
128         *be++ = ' ';
129         if (fps->io == fdio) {
130             sprintf(be, "FD %d fp %p", fps->fdno, fps->fp);
131         } else if (fps->io == ufdio) {
132             sprintf(be, "UFD %d fp %p", fps->fdno, fps->fp);
133         } else if (fps->io == fadio) {
134             sprintf(be, "FAD %d fp %p", fps->fdno, fps->fp);
135         } else if (fps->io == gzdio) {
136             sprintf(be, "GZD %p fdno %d", fps->fp, fps->fdno);
137 #if HAVE_BZLIB_H
138         } else if (fps->io == bzdio) {
139             sprintf(be, "BZD %p fdno %d", fps->fp, fps->fdno);
140 #endif
141         } else if (fps->io == fpio) {
142             sprintf(be, "%s %p(%d) fdno %d",
143                 (fps->fdno < 0 ? "LIBIO" : "FP"),
144                 fps->fp, fileno(((FILE *)fps->fp)), fps->fdno);
145         } else {
146             sprintf(be, "??? io %p fp %p fdno %d ???",
147                 fps->io, fps->fp, fps->fdno);
148         }
149         be += strlen(be);
150         *be = '\0';
151     }
152     return buf;
153 }
154
155 /* =============================================================== */
156 off_t fdSize(FD_t fd) {
157     struct stat sb;
158     off_t rc = -1; 
159
160 #ifdef  NOISY
161 DBGIO(0, (stderr, "==>\tfdSize(%p) rc %ld\n", fd, (long)rc));
162 #endif
163     FDSANE(fd);
164     if (fd->contentLength >= 0)
165         rc = fd->contentLength;
166     else switch (fd->urlType) {
167     case URL_IS_PATH:
168     case URL_IS_UNKNOWN:
169         if (fstat(Fileno(fd), &sb) == 0)
170             rc = sb.st_size;
171         /*@fallthrough@*/
172     case URL_IS_FTP:
173     case URL_IS_HTTP:
174     case URL_IS_DASH:
175         break;
176     }
177     return rc;
178 }
179
180 FD_t fdDup(int fdno) {
181     FD_t fd;
182     int nfdno;
183
184     if ((nfdno = dup(fdno)) < 0)
185         return NULL;
186     fd = fdNew("open (fdDup)");
187     fdSetFdno(fd, nfdno);
188 DBGIO(fd, (stderr, "==> fdDup(%d) fd %p %s\n", fdno, fd, fdbg(fd)));
189     return fd;
190 }
191
192 #ifdef USE_COOKIE_SEEK_POINTER
193 static inline int fdSeekNot(void * cookie,  /*@unused@*/ _IO_off64_t *pos,  /*@unused@*/ int whence) {
194 #else
195 static inline int fdSeekNot(void * cookie,  /*@unused@*/ off_t pos,  /*@unused@*/ int whence) {
196 #endif
197     FD_t fd = c2f(cookie);
198     FDSANE(fd);         /* XXX keep gcc quiet */
199     return -2;
200 }
201
202 #ifdef UNUSED
203 FILE *fdFdopen(void * cookie, const char *fmode) {
204     FD_t fd = c2f(cookie);
205     int fdno;
206     FILE * fp;
207
208     if (fmode == NULL) return NULL;
209     fdno = fdFileno(fd);
210     if (fdno < 0) return NULL;
211     fp = fdopen(fdno, fmode);
212 DBGIO(fd, (stderr, "==> fdFdopen(%p,\"%s\") fdno %d -> fp %p fdno %d\n", cookie, fmode, fdno, fp, fileno(fp)));
213     fd = fdFree(fd, "open (fdFdopen)");
214     return fp;
215 }
216 #endif
217
218 #undef  fdRead
219 #undef  fdWrite
220 #undef  fdSeek
221 #undef  fdClose
222 #if 0
223 #undef  fdLink
224 #undef  fdFree
225 #undef  fdNew
226 #endif
227 #undef  fdFileno
228 #undef  fdOpen
229
230 /* =============================================================== */
231 static inline FD_t XfdLink(void * cookie, const char *msg, const char *file, unsigned line) {
232     FD_t fd;
233 if (cookie == NULL)
234 DBGREFS(0, (stderr, "--> fd  %p ++ %d %s at %s:%u\n", cookie, FDNREFS(cookie)+1, msg, file, line));
235     fd = c2f(cookie);
236     if (fd) {
237         fd->nrefs++;
238 DBGREFS(fd, (stderr, "--> fd  %p ++ %d %s at %s:%u %s\n", fd, fd->nrefs, msg, file, line, fdbg(fd)));
239     }
240     return fd;
241 }
242
243 static inline /*@null@*/ FD_t XfdFree( /*@killref@*/ FD_t fd, const char *msg, const char *file, unsigned line) {
244 if (fd == NULL)
245 DBGREFS(0, (stderr, "--> fd  %p -- %d %s at %s:%u\n", fd, FDNREFS(fd), msg, file, line));
246     FDSANE(fd);
247     if (fd) {
248 DBGREFS(fd, (stderr, "--> fd  %p -- %d %s at %s:%u %s\n", fd, fd->nrefs, msg, file, line, fdbg(fd)));
249         if (--fd->nrefs > 0)
250             /*@-refcounttrans@*/ return fd; /*@=refcounttrans@*/
251         if (fd->stats) free(fd->stats);
252         /*@-refcounttrans@*/ free(fd); /*@=refcounttrans@*/
253     }
254     return NULL;
255 }
256
257 static inline /*@null@*/ FD_t XfdNew(const char *msg, const char *file, unsigned line) {
258     FD_t fd = (FD_t) xmalloc(sizeof(struct _FD_s));
259     if (fd == NULL)
260         return NULL;
261     fd->nrefs = 0;
262     fd->flags = 0;
263     fd->magic = FDMAGIC;
264     fd->urlType = URL_IS_UNKNOWN;
265
266     fd->nfps = 0;
267     memset(fd->fps, 0, sizeof(fd->fps));
268
269     fd->fps[0].io = fdio;
270     fd->fps[0].fp = NULL;
271     fd->fps[0].fdno = -1;
272
273     fd->url = NULL;
274     fd->rd_timeoutsecs = 1;     /* XXX default value used to be -1 */
275     fd->contentLength = fd->bytesRemain = -1;
276     fd->wr_chunked = 0;
277     fd->syserrno = 0;
278     fd->errcookie = NULL;
279     fd->stats = calloc(1, sizeof(FDSTAT_t));
280     gettimeofday(&fd->stats->create, NULL);
281     fd->stats->begin = fd->stats->create;       /* structure assignment */
282
283     fd->ftpFileDoneNeeded = 0;
284     fd->firstFree = 0;
285     fd->fileSize = 0;
286     fd->fd_cpioPos = 0;
287
288     return XfdLink(fd, msg, file, line);
289 }
290
291 static inline ssize_t fdRead(void * cookie, /*@out@*/ char * buf, size_t count) {
292     FD_t fd = c2f(cookie);
293     ssize_t rc;
294
295     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
296
297     fdstat_enter(fd, FDSTAT_READ);
298     rc = read(fdFileno(fd), buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
299     fdstat_exit(fd, FDSTAT_READ, rc);
300
301 DBGIO(fd, (stderr, "==>\tfdRead(%p,%p,%ld) rc %ld %s\n", cookie, buf, (long)count, (long)rc, fdbg(fd)));
302
303     return rc;
304 }
305
306 static inline ssize_t fdWrite(void * cookie, const char * buf, size_t count) {
307     FD_t fd = c2f(cookie);
308     int fdno = fdFileno(fd);
309     ssize_t rc;
310
311     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
312     if (fd->wr_chunked) {
313         char chunksize[20];
314         sprintf(chunksize, "%x\r\n", (unsigned)count);
315         rc = write(fdno, chunksize, strlen(chunksize));
316         if (rc == -1)   fd->syserrno = errno;
317     }
318     if (count == 0) return 0;
319
320     fdstat_enter(fd, FDSTAT_WRITE);
321     rc = write(fdno, buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
322     fdstat_exit(fd, FDSTAT_WRITE, rc);
323
324     if (fd->wr_chunked) {
325         int ec;
326         ec = write(fdno, "\r\n", sizeof("\r\n")-1);
327         if (ec == -1)   fd->syserrno = errno;
328     }
329
330 DBGIO(fd, (stderr, "==>\tfdWrite(%p,%p,%ld) rc %ld %s\n", cookie, buf, (long)count, (long)rc, fdbg(fd)));
331
332     return rc;
333 }
334
335 #ifdef USE_COOKIE_SEEK_POINTER
336 static inline int fdSeek(void * cookie, _IO_off64_t *pos, int whence) {
337     _IO_off64_t p = *pos;
338 #else
339 static inline int fdSeek(void * cookie, off_t p, int whence) {
340 #endif
341     FD_t fd = c2f(cookie);
342     off_t rc;
343
344     assert(fd->bytesRemain == -1);      /* XXX FIXME fadio only for now */
345     fdstat_enter(fd, FDSTAT_SEEK);
346     rc = lseek(fdFileno(fd), p, whence);
347     fdstat_exit(fd, FDSTAT_SEEK, rc);
348
349 DBGIO(fd, (stderr, "==>\tfdSeek(%p,%ld,%d) rc %lx %s\n", cookie, (long)p, whence, (long)rc, fdbg(fd)));
350
351     return rc;
352 }
353
354 static inline int fdClose( /*@only@*/ void * cookie) {
355     FD_t fd;
356     int fdno;
357     int rc;
358
359     if (cookie == NULL) return -2;
360     fd = c2f(cookie);
361     fdno = fdFileno(fd);
362
363     fdSetFdno(fd, -1);
364
365     fdstat_enter(fd, FDSTAT_CLOSE);
366     rc = ((fdno >= 0) ? close(fdno) : -2);
367     fdstat_exit(fd, FDSTAT_CLOSE, rc);
368
369 DBGIO(fd, (stderr, "==>\tfdClose(%p) rc %lx %s\n", fd, (long)rc, fdbg(fd)));
370
371     fd = fdFree(fd, "open (fdClose)");
372     return rc;
373 }
374
375 static inline /*@null@*/ FD_t fdOpen(const char *path, int flags, mode_t mode) {
376     FD_t fd;
377     int fdno;
378
379     fdno = open(path, flags, mode);
380     if (fdno < 0) return NULL;
381     fd = fdNew("open (fdOpen)");
382     fdSetFdno(fd, fdno);
383     fd->flags = flags;
384 DBGIO(fd, (stderr, "==>\tfdOpen(\"%s\",%x,0%o) %s\n", path, flags, (unsigned)mode, fdbg(fd)));
385     return fd;
386 }
387
388 static struct FDIO_s fdio_s = {
389   fdRead, fdWrite, fdSeek, fdClose, XfdLink, XfdFree, XfdNew, fdFileno,
390   fdOpen, NULL, fdGetFp, NULL,  mkdir, chdir, rmdir, rename, unlink
391 };
392 FDIO_t fdio = /*@-compmempass@*/ &fdio_s /*@=compmempass@*/ ;
393
394 int fdWritable(FD_t fd, int secs)
395 {
396     int fdno;
397     fd_set wrfds;
398     struct timeval timeout, *tvp = (secs >= 0 ? &timeout : NULL);
399     int rc;
400         
401     if ((fdno = fdFileno(fd)) < 0)
402         return -1;      /* XXX W2DO? */
403         
404     FD_ZERO(&wrfds);
405     do {
406         FD_SET(fdno, &wrfds);
407
408         if (tvp) {
409             tvp->tv_sec = secs;
410             tvp->tv_usec = 0;
411         }
412         errno = 0;
413         rc = select(fdno + 1, NULL, &wrfds, NULL, tvp);
414
415 if (_rpmio_debug && !(rc == 1 && errno == 0))
416 fprintf(stderr, "*** fdWritable fdno %d rc %d %s\n", fdno, rc, strerror(errno));
417         if (rc < 0) {
418             switch (errno) {
419             case EINTR:
420                 continue;
421                 /*@notreached@*/ break;
422             default:
423                 return rc;
424                 /*@notreached@*/ break;
425             }
426         }
427         return rc;
428     } while (1);
429     /*@notreached@*/
430 }
431
432 int fdReadable(FD_t fd, int secs)
433 {
434     int fdno;
435     fd_set rdfds;
436     struct timeval timeout, *tvp = (secs >= 0 ? &timeout : NULL);
437     int rc;
438
439     if ((fdno = fdFileno(fd)) < 0)
440         return -1;      /* XXX W2DO? */
441         
442     FD_ZERO(&rdfds);
443     do {
444         FD_SET(fdno, &rdfds);
445
446         if (tvp) {
447             tvp->tv_sec = secs;
448             tvp->tv_usec = 0;
449         }
450         errno = 0;
451         rc = select(fdno + 1, &rdfds, NULL, NULL, tvp);
452
453         if (rc < 0) {
454             switch (errno) {
455             case EINTR:
456                 continue;
457                 /*@notreached@*/ break;
458             default:
459                 return rc;
460                 /*@notreached@*/ break;
461             }
462         }
463         return rc;
464     } while (1);
465     /*@notreached@*/
466 }
467
468 static int fdFgets(FD_t fd, char * buf, size_t len)
469 {
470     int fdno;
471     int secs = fd->rd_timeoutsecs;
472     size_t nb = 0;
473     int ec = 0;
474     char lastchar = '\0';
475
476     if ((fdno = fdFileno(fd)) < 0)
477         return 0;       /* XXX W2DO? */
478         
479     do {
480         int rc;
481
482         /* Is there data to read? */
483         rc = fdReadable(fd, secs);
484
485         switch (rc) {
486         case -1:        /* error */
487             ec = -1;
488             continue;
489             /*@notreached@*/ break;
490         case  0:        /* timeout */
491             ec = -1;
492             continue;
493             /*@notreached@*/ break;
494         default:        /* data to read */
495             break;
496         }
497
498         errno = 0;
499 #ifdef  NOISY
500         rc = fdRead(fd, buf + nb, 1);
501 #else
502         rc = read(fdFileno(fd), buf + nb, 1);
503 #endif
504         if (rc < 0) {
505             fd->syserrno = errno;
506             switch (errno) {
507             case EWOULDBLOCK:
508                 continue;
509                 /*@notreached@*/ break;
510             default:
511                 break;
512             }
513 if (_rpmio_debug)
514 fprintf(stderr, "*** read: fd %p rc %d errno %d %s \"%s\"\n", fd, rc, errno, strerror(errno), buf);
515             ec = -1;
516             break;
517         } else if (rc == 0) {
518 if (_rpmio_debug)
519 fprintf(stderr, "*** read: fd %p rc %d EOF errno %d %s \"%s\"\n", fd, rc, errno, strerror(errno), buf);
520             break;
521         } else {
522             nb += rc;
523             buf[nb] = '\0';
524             lastchar = buf[nb - 1];
525         }
526     } while (ec == 0 && nb < len && lastchar != '\n');
527
528     return (ec >= 0 ? nb : ec);
529 }
530
531 /* =============================================================== */
532 /* Support for FTP/HTTP I/O.
533  */
534 const char *const ftpStrerror(int errorNumber) {
535   switch (errorNumber) {
536     case 0:
537         return _("Success");
538
539     case FTPERR_BAD_SERVER_RESPONSE:
540         return _("Bad server response");
541
542     case FTPERR_SERVER_IO_ERROR:
543         return _("Server IO error");
544
545     case FTPERR_SERVER_TIMEOUT:
546         return _("Server timeout");
547
548     case FTPERR_BAD_HOST_ADDR:
549         return _("Unable to lookup server host address");
550
551     case FTPERR_BAD_HOSTNAME:
552         return _("Unable to lookup server host name");
553
554     case FTPERR_FAILED_CONNECT:
555         return _("Failed to connect to server");
556
557     case FTPERR_FAILED_DATA_CONNECT:
558         return _("Failed to establish data connection to server");
559
560     case FTPERR_FILE_IO_ERROR:
561         return _("IO error to local file");
562
563     case FTPERR_PASSIVE_ERROR:
564         return _("Error setting remote server to passive mode");
565
566     case FTPERR_FILE_NOT_FOUND:
567         return _("File not found on server");
568
569     case FTPERR_NIC_ABORT_IN_PROGRESS:
570         return _("Abort in progress");
571
572     case FTPERR_UNKNOWN:
573     default:
574         return _("Unknown or unexpected error");
575   }
576 }
577
578 const char *urlStrerror(const char *url)
579 {
580     const char *retstr;
581     switch (urlIsURL(url)) {
582     case URL_IS_FTP:
583     case URL_IS_HTTP:
584     {   urlinfo u;
585 /* XXX This only works for httpReq/ftpLogin/ftpReq failures */
586         if (urlSplit(url, &u) == 0) {
587             retstr = ftpStrerror(u->openError);
588         } else
589             retstr = "Malformed URL";
590     }   break;
591     default:
592         retstr = strerror(errno);
593         break;
594     }
595     return retstr;
596 }
597
598 #if !defined(USE_ALT_DNS) || !USE_ALT_DNS 
599 static int mygethostbyname(const char * host, struct in_addr * address)
600 {
601     struct hostent * hostinfo;
602
603     hostinfo = /*@-unrecog@*/ gethostbyname(host) /*@=unrecog@*/;
604     if (!hostinfo) return 1;
605
606     memcpy(address, hostinfo->h_addr_list[0], sizeof(*address));
607     return 0;
608 }
609 #endif
610
611 static int getHostAddress(const char * host, struct in_addr * address)
612 {
613     if (isdigit(host[0])) {
614       if (! /*@-unrecog@*/ inet_aton(host, address) /*@=unrecog@*/ ) {
615           return FTPERR_BAD_HOST_ADDR;
616       }
617     } else {
618       if (mygethostbyname(host, address)) {
619           errno = h_errno;
620           return FTPERR_BAD_HOSTNAME;
621       }
622     }
623     
624     return 0;
625 }
626
627 static int tcpConnect(FD_t ctrl, const char *host, int port)
628 {
629     struct sockaddr_in sin;
630     int fdno = -1;
631     int rc;
632
633     sin.sin_family = AF_INET;
634     sin.sin_port = htons(port);
635     sin.sin_addr.s_addr = INADDR_ANY;
636     
637   do {
638     if ((rc = getHostAddress(host, &sin.sin_addr)) < 0)
639         break;
640
641     if ((fdno = socket(sin.sin_family, SOCK_STREAM, IPPROTO_IP)) < 0) {
642         rc = FTPERR_FAILED_CONNECT;
643         break;
644     }
645
646     if (connect(fdno, (struct sockaddr *) &sin, sizeof(sin))) {
647         rc = FTPERR_FAILED_CONNECT;
648         break;
649     }
650   } while (0);
651
652     if (rc < 0)
653         goto errxit;
654
655 if (_ftp_debug)
656 fprintf(stderr,"++ connect %s:%d on fdno %d\n",
657 /*@-unrecog@*/ inet_ntoa(sin.sin_addr) /*@=unrecog@*/ ,
658 ntohs(sin.sin_port), fdno);
659
660     fdSetFdno(ctrl, (fdno >= 0 ? fdno : -1));
661     return 0;
662
663 errxit:
664     fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
665     if (fdno >= 0)
666         close(fdno);
667     return rc;
668 }
669
670 static int checkResponse(void * uu, FD_t ctrl, /*@out@*/ int *ecp, /*@out@*/ char ** str)
671 {
672     urlinfo u = uu;
673     char *buf;
674     size_t bufAlloced;
675     int bufLength = 0; 
676     const char *s;
677     char *se;
678     int ec = 0;
679     int moretodo = 1;
680     char errorCode[4];
681  
682     URLSANE(u);
683     if (u->bufAlloced == 0 || u->buf == NULL) {
684         u->bufAlloced = url_iobuf_size;
685         u->buf = xcalloc(u->bufAlloced, sizeof(char));
686     }
687     buf = u->buf;
688     bufAlloced = u->bufAlloced;
689     *buf = '\0';
690
691     errorCode[0] = '\0';
692     
693     do {
694         int rc;
695
696         /*
697          * Read next line from server.
698          */
699         se = buf + bufLength;
700         *se = '\0';
701         rc = fdFgets(ctrl, se, (bufAlloced - bufLength));
702         if (rc < 0) {
703             ec = FTPERR_BAD_SERVER_RESPONSE;
704             continue;
705         } else if (rc == 0 || fdWritable(ctrl, 0) < 1)
706             moretodo = 0;
707
708         /*
709          * Process next line from server.
710          */
711         for (s = se; *s != '\0'; s = se) {
712                 const char *e;
713
714                 while (*se && *se != '\n') se++;
715
716                 if (se > s && se[-1] == '\r')
717                    se[-1] = '\0';
718                 if (*se == '\0')
719                     break;
720
721 if (_ftp_debug)
722 fprintf(stderr, "<- %s\n", s);
723
724                 /* HTTP: header termination on empty line */
725                 if (*s == '\0') {
726                     moretodo = 0;
727                     break;
728                 }
729                 *se++ = '\0';
730
731                 /* HTTP: look for "HTTP/1.1 123 ..." */
732                 if (!strncmp(s, "HTTP", sizeof("HTTP")-1)) {
733                     ctrl->contentLength = -1;
734                     if ((e = strchr(s, '.')) != NULL) {
735                         e++;
736                         u->httpVersion = *e - '0';
737                         if (u->httpVersion < 1 || u->httpVersion > 2)
738                             ctrl->persist = u->httpVersion = 0;
739                         else
740                             ctrl->persist = 1;
741                     }
742                     if ((e = strchr(s, ' ')) != NULL) {
743                         e++;
744                         if (strchr("0123456789", *e))
745                             strncpy(errorCode, e, 3);
746                         errorCode[3] = '\0';
747                     }
748                     continue;
749                 }
750
751                 /* HTTP: look for "token: ..." */
752                 for (e = s; *e && !(*e == ' ' || *e == ':'); e++)
753                     ;
754                 if (e > s && *e++ == ':') {
755                     size_t ne = (e - s);
756                     while (*e && *e == ' ') e++;
757 #if 0
758                     if (!strncmp(s, "Date:", ne)) {
759                     } else
760                     if (!strncmp(s, "Server:", ne)) {
761                     } else
762                     if (!strncmp(s, "Last-Modified:", ne)) {
763                     } else
764                     if (!strncmp(s, "ETag:", ne)) {
765                     } else
766 #endif
767                     if (!strncmp(s, "Accept-Ranges:", ne)) {
768                         if (!strcmp(e, "bytes"))
769                             u->httpHasRange = 1;
770                         if (!strcmp(e, "none"))
771                             u->httpHasRange = 0;
772                     } else
773                     if (!strncmp(s, "Content-Length:", ne)) {
774                         if (strchr("0123456789", *e))
775                             ctrl->contentLength = atoi(e);
776                     } else
777                     if (!strncmp(s, "Connection:", ne)) {
778                         if (!strcmp(e, "close"))
779                             ctrl->persist = 0;
780                     } else
781 #if 0
782                     if (!strncmp(s, "Content-Type:", ne)) {
783                     } else
784                     if (!strncmp(s, "Transfer-Encoding:", ne)) {
785                         if (!strcmp(e, "chunked"))
786                             ctrl->wr_chunked = 1;
787                         else
788                             ctrl->wr_chunked = 0;
789                     } else
790                     if (!strncmp(s, "Allow:", ne)) {
791                     } else
792 #endif
793                         ;
794                     continue;
795                 }
796
797                 /* HTTP: look for "<TITLE>501 ... </TITLE>" */
798                 if (!strncmp(s, "<TITLE>", sizeof("<TITLE>")-1))
799                     s += sizeof("<TITLE>") - 1;
800
801                 /* FTP: look for "123-" and/or "123 " */
802                 if (strchr("0123456789", *s)) {
803                     if (errorCode[0]) {
804                         if (!strncmp(s, errorCode, sizeof("123")-1) && s[3] == ' ')
805                             moretodo = 0;
806                     } else {
807                         strncpy(errorCode, s, sizeof("123")-1);
808                         errorCode[3] = '\0';
809                         if (s[3] != '-')
810                             moretodo = 0;
811                     }
812                 }
813         }
814
815         if (moretodo && se > s) {
816             bufLength = se - s - 1;
817             if (s != buf)
818                 memmove(buf, s, bufLength);
819         } else {
820             bufLength = 0;
821         }
822     } while (moretodo && ec == 0);
823
824     if (str)    *str = buf;
825     if (ecp)    *ecp = atoi(errorCode);
826
827     return ec;
828 }
829
830 static int ftpCheckResponse(urlinfo u, /*@out@*/ char ** str)
831 {
832     int ec = 0;
833     int rc;
834
835     URLSANE(u);
836     rc = checkResponse(u, u->ctrl, &ec, str);
837
838     switch (ec) {
839     case 550:
840         return FTPERR_FILE_NOT_FOUND;
841         /*@notreached@*/ break;
842     case 552:
843         return FTPERR_NIC_ABORT_IN_PROGRESS;
844         /*@notreached@*/ break;
845     default:
846         if (ec >= 400 && ec <= 599) {
847             return FTPERR_BAD_SERVER_RESPONSE;
848         }
849         break;
850     }
851     return rc;
852 }
853
854 static int ftpCommand(urlinfo u, char ** str, ...)
855 {
856     va_list ap;
857     int len = 0;
858     const char * s, * t;
859     char * te;
860     int rc;
861
862     URLSANE(u);
863     va_start(ap, str);
864     while ((s = va_arg(ap, const char *)) != NULL) {
865         if (len) len++;
866         len += strlen(s);
867     }
868     len += sizeof("\r\n")-1;
869     va_end(ap);
870
871     t = te = alloca(len + 1);
872
873     va_start(ap, str);
874     while ((s = va_arg(ap, const char *)) != NULL) {
875         if (te > t) *te++ = ' ';
876         te = stpcpy(te, s);
877     }
878     te = stpcpy(te, "\r\n");
879     va_end(ap);
880
881 if (_ftp_debug)
882 fprintf(stderr, "-> %s", t);
883     if (fdWrite(u->ctrl, t, (te-t)) != (te-t))
884         return FTPERR_SERVER_IO_ERROR;
885
886     rc = ftpCheckResponse(u, str);
887     return rc;
888 }
889
890 static int ftpLogin(urlinfo u)
891 {
892     const char * host;
893     const char * user;
894     const char * password;
895     int port;
896     int rc;
897
898     URLSANE(u);
899     u->ctrl = fdLink(u->ctrl, "open ctrl");
900
901     if (((host = (u->proxyh ? u->proxyh : u->host)) == NULL)) {
902         rc = FTPERR_BAD_HOSTNAME;
903         goto errxit;
904     }
905
906     if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = IPPORT_FTP;
907
908     if ((user = (u->proxyu ? u->proxyu : u->user)) == NULL)
909         user = "anonymous";
910
911     if ((password = u->password) == NULL) {
912         if (getuid()) {
913             struct passwd * pw = getpwuid(getuid());
914             char *myp = alloca(strlen(pw->pw_name) + sizeof("@"));
915             strcpy(myp, pw->pw_name);
916             strcat(myp, "@");
917             password = myp;
918         } else {
919             password = "root@";
920         }
921     }
922
923     if (fdFileno(u->ctrl) >= 0 && fdWritable(u->ctrl, 0) < 1)
924         fdClose(u->ctrl);
925
926     if (fdFileno(u->ctrl) < 0) {
927         rc = tcpConnect(u->ctrl, host, port);
928         if (rc < 0)
929             goto errxit2;
930     }
931
932     if ((rc = ftpCheckResponse(u, NULL)))
933         goto errxit;
934
935     if ((rc = ftpCommand(u, NULL, "USER", user, NULL)))
936         goto errxit;
937
938     if ((rc = ftpCommand(u, NULL, "PASS", password, NULL)))
939         goto errxit;
940
941     if ((rc = ftpCommand(u, NULL, "TYPE", "I", NULL)))
942         goto errxit;
943
944     return 0;
945
946 errxit:
947     fdSetSyserrno(u->ctrl, errno, ftpStrerror(rc));
948 errxit2:
949     if (fdFileno(u->ctrl) >= 0)
950         fdClose(u->ctrl);
951     return rc;
952 }
953
954 static int ftpReq(FD_t data, const char * ftpCmd, const char * ftpArg)
955 {
956     urlinfo u = data->url;
957     struct sockaddr_in dataAddress;
958     char * cmd;
959     int cmdlen;
960     char * passReply;
961     char * chptr;
962     int rc;
963
964     URLSANE(u);
965     if (ftpCmd == NULL)
966         return FTPERR_UNKNOWN;  /* XXX W2DO? */
967
968     cmdlen = strlen(ftpCmd) + (ftpArg ? 1+strlen(ftpArg) : 0) + sizeof("\r\n");
969     chptr = cmd = alloca(cmdlen);
970     chptr = stpcpy(chptr, ftpCmd);
971     if (ftpArg) {
972         *chptr++ = ' ';
973         chptr = stpcpy(chptr, ftpArg);
974     }
975     chptr = stpcpy(chptr, "\r\n");
976     cmdlen = chptr - cmd;
977
978 /*
979  * Get the ftp version of the Content-Length.
980  */
981     if (!strncmp(cmd, "RETR", 4)) {
982         unsigned cl;
983
984         passReply = NULL;
985         rc = ftpCommand(u, &passReply, "SIZE", ftpArg, NULL);
986         if (rc)
987             goto errxit;
988         if (sscanf(passReply, "%d %u", &rc, &cl) != 2) {
989             rc = FTPERR_BAD_SERVER_RESPONSE;
990             goto errxit;
991         }
992         rc = 0;
993         data->contentLength = cl;
994     }
995
996     passReply = NULL;
997     rc = ftpCommand(u, &passReply, "PASV", NULL);
998     if (rc) {
999         rc = FTPERR_PASSIVE_ERROR;
1000         goto errxit;
1001     }
1002
1003     chptr = passReply;
1004     while (*chptr && *chptr != '(') chptr++;
1005     if (*chptr != '(') return FTPERR_PASSIVE_ERROR; 
1006     chptr++;
1007     passReply = chptr;
1008     while (*chptr && *chptr != ')') chptr++;
1009     if (*chptr != ')') return FTPERR_PASSIVE_ERROR;
1010     *chptr-- = '\0';
1011
1012     while (*chptr && *chptr != ',') chptr--;
1013     if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
1014     chptr--;
1015     while (*chptr && *chptr != ',') chptr--;
1016     if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
1017     *chptr++ = '\0';
1018     
1019     /* now passReply points to the IP portion, and chptr points to the
1020        port number portion */
1021
1022     {   int i, j;
1023         dataAddress.sin_family = AF_INET;
1024         if (sscanf(chptr, "%d,%d", &i, &j) != 2) {
1025             rc = FTPERR_PASSIVE_ERROR;
1026             goto errxit;
1027         }
1028         dataAddress.sin_port = htons((((unsigned)i) << 8) + j);
1029     }
1030
1031     chptr = passReply;
1032     while (*chptr++) {
1033         if (*chptr == ',') *chptr = '.';
1034     }
1035
1036     if (!inet_aton(passReply, &dataAddress.sin_addr)) {
1037         rc = FTPERR_PASSIVE_ERROR;
1038         goto errxit;
1039     }
1040
1041     rc = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
1042     fdSetFdno(data, (rc >= 0 ? rc : -1));
1043     if (rc < 0) {
1044         rc = FTPERR_FAILED_CONNECT;
1045         goto errxit;
1046     }
1047     data = fdLink(data, "open data (ftpReq)");
1048
1049     /* XXX setsockopt SO_LINGER */
1050     /* XXX setsockopt SO_KEEPALIVE */
1051     /* XXX setsockopt SO_TOS IPTOS_THROUGHPUT */
1052
1053     while (connect(fdFileno(data), (struct sockaddr *) &dataAddress, 
1054                 sizeof(dataAddress)) < 0) {
1055         if (errno == EINTR)
1056             continue;
1057         rc = FTPERR_FAILED_DATA_CONNECT;
1058         goto errxit;
1059     }
1060
1061 if (_ftp_debug)
1062 fprintf(stderr, "-> %s", cmd);
1063     if (fdWrite(u->ctrl, cmd, cmdlen) != cmdlen) {
1064         rc = FTPERR_SERVER_IO_ERROR;
1065         goto errxit;
1066     }
1067
1068     if ((rc = ftpCheckResponse(u, NULL))) {
1069         goto errxit;
1070     }
1071
1072     data->ftpFileDoneNeeded = 1;
1073     u->ctrl = fdLink(u->ctrl, "grab data (ftpReq)");
1074     u->ctrl = fdLink(u->ctrl, "open data (ftpReq)");
1075     return 0;
1076
1077 errxit:
1078     fdSetSyserrno(u->ctrl, errno, ftpStrerror(rc));
1079     if (fdFileno(data) >= 0)
1080         fdClose(data);
1081     return rc;
1082 }
1083
1084 static int urlConnect(const char * url, /*@out@*/ urlinfo * uret)
1085 {
1086     urlinfo u;
1087     int rc = 0;
1088
1089     if (urlSplit(url, &u) < 0)
1090         return -1;
1091
1092     if (u->urltype == URL_IS_FTP) {
1093         FD_t fd;
1094
1095         if ((fd = u->ctrl) == NULL) {
1096             fd = u->ctrl = fdNew("persist ctrl (urlConnect FTP)");
1097             fdSetIo(u->ctrl, ufdio);
1098         }
1099         
1100         fd->rd_timeoutsecs = ftpTimeoutSecs;
1101         fd->contentLength = fd->bytesRemain = -1;
1102         fd->url = NULL;         /* XXX FTP ctrl has not */
1103         fd->ftpFileDoneNeeded = 0;
1104         fd = fdLink(fd, "grab ctrl (urlConnect FTP)");
1105
1106         if (fdFileno(u->ctrl) < 0) {
1107             rpmMessage(RPMMESS_DEBUG, _("logging into %s as %s, pw %s\n"),
1108                         u->host,
1109                         u->user ? u->user : "ftp",
1110                         u->password ? u->password : "(username)");
1111
1112             if ((rc = ftpLogin(u)) < 0) {       /* XXX save ftpLogin error */
1113                 u->ctrl = fdFree(fd, "grab ctrl (urlConnect FTP)");
1114                 u->openError = rc;
1115             }
1116         }
1117     }
1118
1119     if (uret != NULL)
1120         *uret = urlLink(u, "urlConnect");
1121     u = urlFree(u, "urlSplit (urlConnect)");    
1122
1123     return rc;
1124 }
1125
1126 /*@null@*/ static rpmCallbackFunction   urlNotify = NULL;
1127 /*@null@*/ static void *        urlNotifyData = NULL;
1128 static int                      urlNotifyCount = -1;
1129
1130 void urlSetCallback(rpmCallbackFunction notify, void *notifyData, int notifyCount) {
1131     urlNotify = notify;
1132     urlNotifyData = notifyData;
1133     urlNotifyCount = (notifyCount >= 0) ? notifyCount : 4096;
1134 }
1135
1136 int ufdCopy(FD_t sfd, FD_t tfd)
1137 {
1138     char buf[BUFSIZ];
1139     int itemsRead;
1140     int itemsCopied = 0;
1141     int rc = 0;
1142     int notifier = -1;
1143
1144     if (urlNotify) {
1145         (void)(*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
1146                 0, 0, NULL, urlNotifyData);
1147     }
1148     
1149     while (1) {
1150         rc = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd);
1151         if (rc < 0)
1152             break;
1153         else if (rc == 0) {
1154             rc = itemsCopied;
1155             break;
1156         }
1157         itemsRead = rc;
1158         rc = Fwrite(buf, sizeof(buf[0]), itemsRead, tfd);
1159         if (rc < 0)
1160             break;
1161         if (rc != itemsRead) {
1162             rc = FTPERR_FILE_IO_ERROR;
1163             break;
1164         }
1165
1166         itemsCopied += itemsRead;
1167         if (urlNotify && urlNotifyCount > 0) {
1168             int n = itemsCopied/urlNotifyCount;
1169             if (n != notifier) {
1170                 (void)(*urlNotify) (NULL, RPMCALLBACK_INST_PROGRESS,
1171                         itemsCopied, 0, NULL, urlNotifyData);
1172                 notifier = n;
1173             }
1174         }
1175     }
1176
1177     DBGIO(sfd, (stderr, "++ copied %d bytes: %s\n", itemsCopied,
1178         ftpStrerror(rc)));
1179
1180     if (urlNotify) {
1181         (void)(*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
1182                 itemsCopied, itemsCopied, NULL, urlNotifyData);
1183     }
1184     
1185     return rc;
1186 }
1187
1188 int ufdGetFile(FD_t sfd, FD_t tfd)
1189 {
1190     int rc;
1191
1192     FDSANE(sfd);
1193     FDSANE(tfd);
1194     rc = ufdCopy(sfd, tfd);
1195     Fclose(sfd);
1196     if (rc > 0)         /* XXX ufdCopy now returns no. bytes copied */
1197         rc = 0;
1198     return rc;
1199 }
1200
1201 static int ftpCmd(const char * cmd, const char * url, const char * arg2) {
1202     urlinfo u;
1203     int rc;
1204     const char * path;
1205
1206     if (urlConnect(url, &u) < 0)
1207         return -1;
1208
1209     (void) urlPath(url, &path);
1210
1211     rc = ftpCommand(u, NULL, cmd, path, arg2, NULL);
1212     u->ctrl = fdFree(u->ctrl, "grab ctrl (ftpCmd)");
1213     return rc;
1214 }
1215
1216 static int ftpMkdir(const char * path, /*@unused@*/ mode_t mode) {
1217     int rc;
1218     if ((rc = ftpCmd("MKD", path, NULL)) != 0)
1219         return rc;
1220 #if NOTYET
1221     {   char buf[20];
1222         sprintf(buf, " 0%o", mode);
1223         (void) ftpCmd("SITE CHMOD", path, buf);
1224     }
1225 #endif
1226     return rc;
1227 }
1228
1229 static int ftpChdir(const char * path) {
1230     return ftpCmd("CWD", path, NULL);
1231 }
1232
1233 static int ftpRmdir(const char * path) {
1234     return ftpCmd("RMD", path, NULL);
1235 }
1236
1237 static int ftpRename(const char * oldpath, const char * newpath) {
1238     int rc;
1239     if ((rc = ftpCmd("RNFR", oldpath, NULL)) != 0)
1240         return rc;
1241     return ftpCmd("RNTO", newpath, NULL);
1242 }
1243
1244 static int ftpUnlink(const char * path) {
1245     return ftpCmd("DELE", path, NULL);
1246 }
1247
1248 /* XXX these aren't worth the pain of including correctly */
1249 #if !defined(IAC)
1250 #define IAC     255             /* interpret as command: */
1251 #endif
1252 #if !defined(IP)
1253 #define IP      244             /* interrupt process--permanently */
1254 #endif
1255 #if !defined(DM)
1256 #define DM      242             /* data mark--for connect. cleaning */
1257 #endif
1258 #if !defined(SHUT_RDWR)
1259 #define SHUT_RDWR       1+1
1260 #endif
1261
1262 static int ftpAbort(urlinfo u, FD_t data) {
1263     static unsigned char ipbuf[3] = { IAC, IP, IAC };
1264     FD_t ctrl;
1265     int rc;
1266     int tosecs;
1267
1268     URLSANE(u);
1269
1270     if (data != NULL) {
1271         data->ftpFileDoneNeeded = 0;
1272         if (fdFileno(data) >= 0)
1273             u->ctrl = fdFree(u->ctrl, "open data (ftpAbort)");
1274         u->ctrl = fdFree(u->ctrl, "grab data (ftpAbort)");
1275     }
1276     ctrl = u->ctrl;
1277
1278     DBGIO(0, (stderr, "-> ABOR\n"));
1279
1280     if (send(fdFileno(ctrl), ipbuf, sizeof(ipbuf), MSG_OOB) != sizeof(ipbuf)) {
1281         fdClose(ctrl);
1282         return FTPERR_SERVER_IO_ERROR;
1283     }
1284
1285     sprintf(u->buf, "%cABOR\r\n",(char) DM);
1286     if (fdWrite(ctrl, u->buf, 7) != 7) {
1287         fdClose(ctrl);
1288         return FTPERR_SERVER_IO_ERROR;
1289     }
1290
1291     if (data && fdFileno(data) >= 0) {
1292         /* XXX shorten data drain time wait */
1293         tosecs = data->rd_timeoutsecs;
1294         data->rd_timeoutsecs = 10;
1295         if (fdReadable(data, data->rd_timeoutsecs) > 0) {
1296             while (timedRead(data, u->buf, u->bufAlloced) > 0)
1297                 ;
1298         }
1299         data->rd_timeoutsecs = tosecs;
1300         /* XXX ftp abort needs to close the data channel to receive status */
1301         shutdown(fdFileno(data), SHUT_RDWR);
1302         close(fdFileno(data));
1303         data->fps[0].fdno = -1; /* XXX WRONG but expedient */
1304     }
1305
1306     /* XXX shorten ctrl drain time wait */
1307     tosecs = u->ctrl->rd_timeoutsecs;
1308     u->ctrl->rd_timeoutsecs = 10;
1309     if ((rc = ftpCheckResponse(u, NULL)) == FTPERR_NIC_ABORT_IN_PROGRESS) {
1310         rc = ftpCheckResponse(u, NULL);
1311     }
1312     rc = ftpCheckResponse(u, NULL);
1313     u->ctrl->rd_timeoutsecs = tosecs;
1314
1315     return rc;
1316 }
1317
1318 static int ftpFileDone(urlinfo u, FD_t data)
1319 {
1320     int rc = 0;
1321
1322     URLSANE(u);
1323     assert(data->ftpFileDoneNeeded);
1324
1325     if (data->ftpFileDoneNeeded) {
1326         data->ftpFileDoneNeeded = 0;
1327         u->ctrl = fdFree(u->ctrl, "open data (ftpFileDone)");
1328         u->ctrl = fdFree(u->ctrl, "grab data (ftpFileDone)");
1329         rc = ftpCheckResponse(u, NULL);
1330     }
1331     return rc;
1332 }
1333
1334 static int httpResp(urlinfo u, FD_t ctrl, /*@out@*/ char ** str)
1335 {
1336     int ec = 0;
1337     int rc;
1338
1339     URLSANE(u);
1340     rc = checkResponse(u, ctrl, &ec, str);
1341
1342 if (_ftp_debug && !(rc == 0 && ec == 200))
1343 fprintf(stderr, "*** httpResp: rc %d ec %d\n", rc, ec);
1344
1345     switch (ec) {
1346     case 200:
1347         break;
1348     default:
1349         rc = FTPERR_FILE_NOT_FOUND;
1350         break;
1351     }
1352
1353     return rc;
1354 }
1355
1356 static int httpReq(FD_t ctrl, const char * httpCmd, const char * httpArg)
1357 {
1358     urlinfo u = ctrl->url;
1359     const char * host;
1360     const char * path;
1361     int port;
1362     int rc;
1363     char * req;
1364     size_t len;
1365     int retrying = 0;
1366
1367     URLSANE(u);
1368     assert(ctrl != NULL);
1369
1370     if (((host = (u->proxyh ? u->proxyh : u->host)) == NULL))
1371         return FTPERR_BAD_HOSTNAME;
1372
1373     if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = 80;
1374     path = (u->proxyh || u->proxyp > 0) ? u->url : httpArg;
1375
1376 reopen:
1377     if (fdFileno(ctrl) >= 0 && (rc = fdWritable(ctrl, 0)) < 1) {
1378         fdClose(ctrl);
1379     }
1380
1381     if (fdFileno(ctrl) < 0) {
1382         rc = tcpConnect(ctrl, host, port);
1383         if (rc < 0)
1384             goto errxit2;
1385         ctrl = fdLink(ctrl, "open ctrl (httpReq)");
1386     }
1387
1388     len = sizeof("\
1389 req x HTTP/1.0\r\n\
1390 User-Agent: rpm/3.0.4\r\n\
1391 Host: y:z\r\n\
1392 Accept: text/plain\r\n\
1393 Transfer-Encoding: chunked\r\n\
1394 \r\n\
1395 ") + strlen(httpCmd) + strlen(path) + sizeof(VERSION) + strlen(host) + 20;
1396
1397     req = alloca(len);
1398     *req = '\0';
1399
1400   if (!strcmp(httpCmd, "PUT")) {
1401     sprintf(req, "\
1402 %s %s HTTP/1.%d\r\n\
1403 User-Agent: rpm/%s\r\n\
1404 Host: %s:%d\r\n\
1405 Accept: text/plain\r\n\
1406 Transfer-Encoding: chunked\r\n\
1407 \r\n\
1408 ",      httpCmd, path, (u->httpVersion ? 1 : 0), VERSION, host, port);
1409 } else {
1410     sprintf(req, "\
1411 %s %s HTTP/1.%d\r\n\
1412 User-Agent: rpm/%s\r\n\
1413 Host: %s:%d\r\n\
1414 Accept: text/plain\r\n\
1415 \r\n\
1416 ",      httpCmd, path, (u->httpVersion ? 1 : 0), VERSION, host, port);
1417 }
1418
1419 if (_ftp_debug)
1420 fprintf(stderr, "-> %s", req);
1421
1422     len = strlen(req);
1423     if (fdWrite(ctrl, req, len) != len) {
1424         rc = FTPERR_SERVER_IO_ERROR;
1425         goto errxit;
1426     }
1427
1428     if (!strcmp(httpCmd, "PUT")) {
1429         ctrl->wr_chunked = 1;
1430     } else {
1431
1432         rc = httpResp(u, ctrl, NULL);
1433
1434         if (rc) {
1435             if (!retrying) {    /* not HTTP_OK */
1436                 retrying = 1;
1437                 fdClose(ctrl);
1438                 goto reopen;
1439             }
1440             goto errxit;
1441         }
1442     }
1443
1444     ctrl = fdLink(ctrl, "open data (httpReq)");
1445     return 0;
1446
1447 errxit:
1448     fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
1449 errxit2:
1450     if (fdFileno(ctrl) >= 0)
1451         fdClose(ctrl);
1452     return rc;
1453 }
1454
1455 /* XXX DYING: unused */
1456 void * ufdGetUrlinfo(FD_t fd) {
1457     FDSANE(fd);
1458     if (fd->url == NULL)
1459         return NULL;
1460     return urlLink(fd->url, "ufdGetUrlinfo");
1461 }
1462
1463 /* =============================================================== */
1464 static ssize_t ufdRead(void * cookie, /*@out@*/ char * buf, size_t count) {
1465     FD_t fd = c2f(cookie);
1466     int bytesRead;
1467     int total;
1468
1469     /* XXX preserve timedRead() behavior */
1470     if (fdGetIo(fd) == fdio) {
1471         struct stat sb;
1472         int fdno = fdFileno(fd);
1473         fstat(fdno, &sb);
1474         if (S_ISREG(sb.st_mode))
1475             return fdRead(fd, buf, count);
1476     }
1477
1478     UFDONLY(fd);
1479     assert(fd->rd_timeoutsecs >= 0);
1480
1481     for (total = 0; total < count; total += bytesRead) {
1482
1483         int rc;
1484
1485         bytesRead = 0;
1486
1487         /* Is there data to read? */
1488         if (fd->bytesRemain == 0) return total; /* XXX simulate EOF */
1489         rc = fdReadable(fd, fd->rd_timeoutsecs);
1490
1491         switch (rc) {
1492         case -1:        /* error */
1493         case  0:        /* timeout */
1494             return total;
1495             /*@notreached@*/ break;
1496         default:        /* data to read */
1497             break;
1498         }
1499
1500         rc = fdRead(fd, buf + total, count - total);
1501
1502         if (rc < 0) {
1503             switch (errno) {
1504             case EWOULDBLOCK:
1505                 continue;
1506                 /*@notreached@*/ break;
1507             default:
1508                 break;
1509             }
1510 if (_rpmio_debug)
1511 fprintf(stderr, "*** read: rc %d errno %d %s \"%s\"\n", rc, errno, strerror(errno), buf);
1512             return rc;
1513             /*@notreached@*/ break;
1514         } else if (rc == 0) {
1515             return total;
1516             /*@notreached@*/ break;
1517         }
1518         bytesRead = rc;
1519     }
1520
1521     return count;
1522 }
1523
1524 static ssize_t ufdWrite(void * cookie, const char * buf, size_t count)
1525 {
1526     FD_t fd = c2f(cookie);
1527     int bytesWritten;
1528     int total = 0;
1529
1530 #ifdef  NOTYET
1531     if (fdGetIo(fd) == fdio) {
1532         struct stat sb;
1533         fstat(fdGetFdno(fd), &sb);
1534         if (S_ISREG(sb.st_mode))
1535             return fdWrite(fd, buf, count);
1536     }
1537 #endif
1538
1539     UFDONLY(fd);
1540
1541     for (total = 0; total < count; total += bytesWritten) {
1542
1543         int rc;
1544
1545         bytesWritten = 0;
1546
1547         /* Is there room to write data? */
1548         if (fd->bytesRemain == 0) {
1549 fprintf(stderr, "*** ufdWrite fd %p WRITE PAST END OF CONTENT\n", fd);
1550             return total;       /* XXX simulate EOF */
1551         }
1552         rc = fdWritable(fd, 2);         /* XXX configurable? */
1553
1554         switch (rc) {
1555         case -1:        /* error */
1556         case  0:        /* timeout */
1557             return total;
1558             /*@notreached@*/ break;
1559         default:        /* data to write */
1560             break;
1561         }
1562
1563         rc = fdWrite(fd, buf + total, count - total);
1564
1565         if (rc < 0) {
1566             switch (errno) {
1567             case EWOULDBLOCK:
1568                 continue;
1569                 /*@notreached@*/ break;
1570             default:
1571                 break;
1572             }
1573 if (_rpmio_debug)
1574 fprintf(stderr, "*** write: rc %d errno %d %s \"%s\"\n", rc, errno, strerror(errno), buf);
1575             return rc;
1576             /*@notreached@*/ break;
1577         } else if (rc == 0) {
1578             return total;
1579             /*@notreached@*/ break;
1580         }
1581         bytesWritten = rc;
1582     }
1583
1584     return count;
1585 }
1586
1587 #ifdef USE_COOKIE_SEEK_POINTER
1588 static inline int ufdSeek(void * cookie, _IO_off64_t *pos, int whence) {
1589 #else
1590 static inline int ufdSeek(void * cookie, off_t pos, int whence) {
1591 #endif
1592     FD_t fd = c2f(cookie);
1593
1594     switch (fd->urlType) {
1595     case URL_IS_UNKNOWN:
1596     case URL_IS_PATH:
1597         break;
1598     case URL_IS_DASH:
1599     case URL_IS_FTP:
1600     case URL_IS_HTTP:
1601     default:
1602         return -2;
1603         /*@notreached@*/ break;
1604     }
1605     return fdSeek(cookie, pos, whence);
1606 }
1607
1608 static int ufdClose( /*@only@*/ void * cookie)
1609 {
1610     FD_t fd = c2f(cookie);
1611
1612     UFDONLY(fd);
1613
1614     if (fd->url) {
1615         urlinfo u = fd->url;
1616
1617         if (fd == u->data)
1618                 fd = u->data = fdFree(fd, "grab data (ufdClose persist)");
1619         else
1620                 fd = fdFree(fd, "grab data (ufdClose)");
1621         (void) urlFree(fd->url, "url (ufdClose)");
1622         fd->url = NULL;
1623         u->ctrl = fdFree(u->ctrl, "grab ctrl (ufdClose)");
1624
1625         if (u->urltype == URL_IS_FTP) {
1626
1627             /* XXX if not using libio, lose the fp from fpio */
1628             {   FILE * fp = fdGetFILE(fd);
1629                 if (noLibio && fp)
1630                     fdSetFp(fd, NULL);
1631             }
1632
1633             /*
1634              * Normal FTP has 4 refs on the data fd:
1635              *  "persist data (ufdOpen FTP)"            rpmio.c:888
1636              *  "grab data (ufdOpen FTP)"               rpmio.c:892
1637              *  "open data (ftpReq)"                    ftp.c:633
1638              *  "fopencookie"                           rpmio.c:1507
1639              */
1640
1641             /*
1642              * Normal FTP has 5 refs on the ctrl fd:
1643              *  "persist ctrl"                          url.c:176
1644              *  "grab ctrl (urlConnect FTP)"            rpmio.c:404
1645              *  "open ctrl"                             ftp.c:504
1646              *  "grab data (ftpReq)"                    ftp.c:661
1647              *  "open data (ftpReq)"                    ftp.c:662
1648              */
1649             if (fd->bytesRemain > 0) {
1650                 if (fd->ftpFileDoneNeeded) {
1651                     if (fdReadable(u->ctrl, 0) > 0)
1652                         ftpFileDone(u, fd);
1653                     else
1654                         ftpAbort(u, fd);
1655                 }
1656             } else {
1657                 int rc;
1658                 /* XXX STOR et al require close before ftpFileDone */
1659                 rc = fdClose(fd);
1660 #if 0   /* XXX error exit from ufdOpen does not have this set */
1661                 assert(fd->ftpFileDoneNeeded != 0);
1662 #endif
1663                 if (fd->ftpFileDoneNeeded)
1664                     ftpFileDone(u, fd);
1665                 return rc;
1666             }
1667         }
1668
1669         if (!strcmp(u->service, "http")) {
1670             if (fd->wr_chunked) {
1671                 int rc;
1672             /* XXX HTTP PUT requires terminating 0 length chunk. */
1673                 fdWrite(fd, NULL, 0);
1674                 fd->wr_chunked = 0;
1675             /* XXX HTTP PUT requires terminating entity-header. */
1676 if (_ftp_debug)
1677 fprintf(stderr, "-> \r\n");
1678                 (void) fdWrite(fd, "\r\n", sizeof("\r\n")-1);
1679                 rc = httpResp(u, fd, NULL);
1680             }
1681
1682             if (fd == u->ctrl)
1683                 fd = u->ctrl = fdFree(fd, "open data (ufdClose HTTP persist ctrl)");
1684             else if (fd == u->data)
1685                 fd = u->data = fdFree(fd, "open data (ufdClose HTTP persist data)");
1686             else
1687                 fd = fdFree(fd, "open data (ufdClose HTTP)");
1688
1689             /*
1690              * HTTP has 4 (or 5 if persistent malloc) refs on the fd:
1691              *  "persist ctrl"                          url.c:177
1692              *  "grab ctrl (ufdOpen HTTP)"              rpmio.c:924
1693              *  "grab data (ufdOpen HTTP)"              rpmio.c:928
1694              *  "open ctrl (httpReq)"                   ftp.c:382
1695              *  "open data (httpReq)"                   ftp.c:435
1696              */
1697
1698             /* XXX if not using libio, lose the fp from fpio */
1699             {   FILE * fp = fdGetFILE(fd);
1700                 if (noLibio && fp)
1701                     fdSetFp(fd, NULL);
1702             }
1703
1704             if (fd->persist && u->httpVersion &&
1705                 (fd == u->ctrl || fd == u->data) && fd->bytesRemain == 0) {
1706                 fd->contentLength = fd->bytesRemain = -1;
1707                 return 0;
1708             } else {
1709                 fd->contentLength = fd->bytesRemain = -1;
1710             }
1711         }
1712     }
1713     return fdClose(fd);
1714 }
1715
1716 static /*@null@*/ FD_t ftpOpen(const char *url, /*@unused@*/ int flags,
1717                 /*@unused@*/ mode_t mode, /*@out@*/ urlinfo *uret)
1718 {
1719     urlinfo u = NULL;
1720     FD_t fd = NULL;
1721
1722 #if 0   /* XXX makeTempFile() heartburn */
1723     assert(!(flags & O_RDWR));
1724 #endif
1725     if (urlConnect(url, &u) < 0)
1726         goto exit;
1727
1728     if (u->data == NULL)
1729         u->data = fdNew("persist data (ftpOpen)");
1730
1731     if (u->data->url == NULL)
1732         fd = fdLink(u->data, "grab data (ftpOpen persist data)");
1733     else
1734         fd = fdNew("grab data (ftpOpen)");
1735
1736     if (fd) {
1737         fdSetIo(fd, ufdio);
1738         fd->ftpFileDoneNeeded = 0;
1739         fd->rd_timeoutsecs = ftpTimeoutSecs;
1740         fd->contentLength = fd->bytesRemain = -1;
1741         fd->url = urlLink(u, "url (ufdOpen FTP)");
1742         fd->urlType = URL_IS_FTP;
1743     }
1744
1745 exit:
1746     if (uret)
1747         *uret = u;
1748     return fd;
1749 }
1750
1751 static /*@null@*/ FD_t httpOpen(const char *url, int flags, mode_t mode,
1752                 /*@out@*/ urlinfo *uret)
1753 {
1754     urlinfo u = NULL;
1755     FD_t fd = NULL;
1756
1757 #if 0   /* XXX makeTempFile() heartburn */
1758     assert(!(flags & O_RDWR));
1759 #endif
1760     if (urlSplit(url, &u))
1761         goto exit;
1762
1763     if (u->ctrl == NULL)
1764         u->ctrl = fdNew("persist ctrl (httpOpen)");
1765     if (u->ctrl->nrefs > 2 && u->data == NULL)
1766         u->data = fdNew("persist data (httpOpen)");
1767
1768     if (u->ctrl->url == NULL)
1769         fd = fdLink(u->ctrl, "grab ctrl (httpOpen persist ctrl)");
1770     else if (u->data->url == NULL)
1771         fd = fdLink(u->data, "grab ctrl (httpOpen persist data)");
1772     else
1773         fd = fdNew("grab ctrl (httpOpen)");
1774
1775     if (fd) {
1776         fdSetIo(fd, ufdio);
1777         fd->ftpFileDoneNeeded = 0;
1778         fd->rd_timeoutsecs = httpTimeoutSecs;
1779         fd->contentLength = fd->bytesRemain = -1;
1780         fd->url = urlLink(u, "url (httpOpen)");
1781         fd = fdLink(fd, "grab data (httpOpen)");
1782         fd->urlType = URL_IS_HTTP;
1783     }
1784
1785 exit:
1786     if (uret)
1787         *uret = u;
1788     return fd;
1789 }
1790
1791 static /*@null@*/ FD_t ufdOpen(const char *url, int flags, mode_t mode)
1792 {
1793     FD_t fd = NULL;
1794     const char * cmd;
1795     urlinfo u;
1796     const char * path;
1797     urltype urlType = urlPath(url, &path);
1798
1799 if (_rpmio_debug)
1800 fprintf(stderr, "*** ufdOpen(%s,0x%x,0%o)\n", url, flags, (unsigned)mode);
1801
1802     switch (urlType) {
1803     case URL_IS_FTP:
1804         fd = ftpOpen(url, flags, mode, &u);
1805         if (fd == NULL || u == NULL)
1806             break;
1807
1808         /* XXX W2DO? use STOU rather than STOR to prevent clobbering */
1809         cmd = ((flags & O_WRONLY) 
1810                 ?  ((flags & O_APPEND) ? "APPE" :
1811                    ((flags & O_CREAT) ? "STOR" : "STOR"))
1812                 :  ((flags & O_CREAT) ? "STOR" : "RETR"));
1813         u->openError = ftpReq(fd, cmd, path);
1814         if (u->openError < 0) {
1815             /* XXX make sure that we can exit through ufdClose */
1816             fd = fdLink(fd, "error data (ufdOpen FTP)");
1817         } else {
1818             fd->bytesRemain = ((!strcmp(cmd, "RETR"))
1819                 ?  fd->contentLength : -1);
1820             fd->wr_chunked = 0;
1821         }
1822         break;
1823     case URL_IS_HTTP:
1824         fd = httpOpen(url, flags, mode, &u);
1825         if (fd == NULL || u == NULL)
1826             break;
1827
1828         cmd = ((flags & O_WRONLY)
1829                 ?  ((flags & O_APPEND) ? "PUT" :
1830                    ((flags & O_CREAT) ? "PUT" : "PUT"))
1831                 : "GET");
1832         u->openError = httpReq(fd, cmd, path);
1833         if (u->openError < 0) {
1834             /* XXX make sure that we can exit through ufdClose */
1835             fd = fdLink(fd, "error ctrl (ufdOpen HTTP)");
1836             fd = fdLink(fd, "error data (ufdOpen HTTP)");
1837         } else {
1838             fd->bytesRemain = ((!strcmp(cmd, "GET"))
1839                 ?  fd->contentLength : -1);
1840             fd->wr_chunked = ((!strcmp(cmd, "PUT"))
1841                 ?  fd->wr_chunked : 0);
1842         }
1843         break;
1844     case URL_IS_DASH:
1845         assert(!(flags & O_RDWR));
1846         fd = fdDup( ((flags & O_WRONLY) ? STDOUT_FILENO : STDIN_FILENO) );
1847         if (fd) {
1848             fdSetIo(fd, ufdio);
1849             fd->rd_timeoutsecs = 600;   /* XXX W2DO? 10 mins? */
1850             fd->contentLength = fd->bytesRemain = -1;
1851         }
1852         break;
1853     case URL_IS_PATH:
1854     case URL_IS_UNKNOWN:
1855     default:
1856         fd = fdOpen(path, flags, mode);
1857         if (fd) {
1858             fdSetIo(fd, ufdio);
1859             fd->rd_timeoutsecs = 1;
1860             fd->contentLength = fd->bytesRemain = -1;
1861         }
1862         break;
1863     }
1864
1865     if (fd == NULL) return NULL;
1866     fd->urlType = urlType;
1867     if (Fileno(fd) < 0) {
1868         ufdClose(fd);
1869         return NULL;
1870     }
1871 DBGIO(fd, (stderr, "==>\tufdOpen(\"%s\",%x,0%o) %s\n", url, flags, (unsigned)mode, fdbg(fd)));
1872     return fd;
1873 }
1874
1875 static struct FDIO_s ufdio_s = {
1876   ufdRead, ufdWrite, ufdSeek, ufdClose, XfdLink, XfdFree, XfdNew, fdFileno,
1877   ufdOpen, NULL, fdGetFp, NULL, Mkdir, Chdir, Rmdir, Rename, Unlink
1878 };
1879 FDIO_t ufdio = /*@-compmempass@*/ &ufdio_s /*@=compmempass@*/ ;
1880
1881 /* =============================================================== */
1882 /* Support for GZIP library.
1883  */
1884 #ifdef  HAVE_ZLIB_H
1885
1886 #include <zlib.h>
1887
1888 static inline /*@dependent@*/ /*@null@*/ void * gzdFileno(FD_t fd) {
1889     void * rc = NULL;
1890     int i;
1891
1892     FDSANE(fd);
1893     for (i = fd->nfps; i >= 0; i--) {
1894         FDSTACK_t * fps = &fd->fps[i];
1895         if (fps->io != gzdio)
1896             continue;
1897         rc = fps->fp;
1898         break;
1899     }
1900     
1901     return rc;
1902 }
1903
1904 static /*@null@*/ FD_t gzdOpen(const char *path, const char *fmode) {
1905     FD_t fd;
1906     gzFile *gzfile;
1907     if ((gzfile = gzopen(path, fmode)) == NULL)
1908         return NULL;
1909     fd = fdNew("open (gzdOpen)");
1910     fdPop(fd); fdPush(fd, gzdio, gzfile, -1);
1911     
1912 DBGIO(fd, (stderr, "==>\tgzdOpen(\"%s\", \"%s\") fd %p %s\n", path, fmode, fd, fdbg(fd)));
1913     return fdLink(fd, "gzdOpen");
1914 }
1915
1916 static /*@null@*/ FD_t gzdFdopen(void * cookie, const char *fmode) {
1917     FD_t fd = c2f(cookie);
1918     int fdno;
1919     gzFile *gzfile;
1920
1921     if (fmode == NULL) return NULL;
1922     fdno = fdFileno(fd);
1923     fdSetFdno(fd, -1);          /* XXX skip the fdio close */
1924     if (fdno < 0) return NULL;
1925     gzfile = gzdopen(fdno, fmode);
1926     if (gzfile == NULL) return NULL;
1927
1928     fdPush(fd, gzdio, gzfile, fdno);            /* Push gzdio onto stack */
1929
1930     return fdLink(fd, "gzdFdopen");
1931 }
1932
1933 static int gzdFlush(FD_t fd) {
1934     return gzflush(gzdFileno(fd), Z_SYNC_FLUSH);        /* XXX W2DO? */
1935 }
1936
1937 /* =============================================================== */
1938 static ssize_t gzdRead(void * cookie, /*@out@*/ char * buf, size_t count) {
1939     FD_t fd = c2f(cookie);
1940     gzFile *gzfile;
1941     ssize_t rc;
1942
1943     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
1944     gzfile = gzdFileno(fd);
1945     fdstat_enter(fd, FDSTAT_READ);
1946     rc = gzread(gzfile, buf, count);
1947 DBGIO(fd, (stderr, "==>\tgzdRead(%p,%p,%u) rc %lx %s\n", cookie, buf, (unsigned)count, (long)rc, fdbg(fd)));
1948     if (rc < 0) {
1949         int zerror = 0;
1950         fd->errcookie = gzerror(gzfile, &zerror);
1951         if (zerror == Z_ERRNO) {
1952             fd->syserrno = errno;
1953             fd->errcookie = strerror(fd->syserrno);
1954         }
1955     } else if (rc >= 0) {
1956         fdstat_exit(fd, FDSTAT_READ, rc);
1957     }
1958     return rc;
1959 }
1960
1961 static ssize_t gzdWrite(void * cookie, const char * buf, size_t count) {
1962     FD_t fd = c2f(cookie);
1963     gzFile *gzfile;
1964     ssize_t rc;
1965
1966     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
1967     gzfile = gzdFileno(fd);
1968     fdstat_enter(fd, FDSTAT_WRITE);
1969     rc = gzwrite(gzfile, (void *)buf, count);
1970 DBGIO(fd, (stderr, "==>\tgzdWrite(%p,%p,%u) rc %lx %s\n", cookie, buf, (unsigned)count, (long)rc, fdbg(fd)));
1971     if (rc < 0) {
1972         int zerror = 0;
1973         fd->errcookie = gzerror(gzfile, &zerror);
1974         if (zerror == Z_ERRNO) {
1975             fd->syserrno = errno;
1976             fd->errcookie = strerror(fd->syserrno);
1977         }
1978     } else if (rc > 0) {
1979         fdstat_exit(fd, FDSTAT_WRITE, rc);
1980     }
1981     return rc;
1982 }
1983
1984 /* XXX zlib-1.0.4 has not */
1985 #ifdef USE_COOKIE_SEEK_POINTER
1986 static inline int gzdSeek(void * cookie, _IO_off64_t *pos, int whence) {
1987     _IO_off64_t p = *pos;
1988 #else
1989 static inline int gzdSeek(void * cookie, off_t p, int whence) {
1990 #endif
1991     int rc;
1992 #if HAVE_GZSEEK
1993     FD_t fd = c2f(cookie);
1994     gzFile *gzfile;
1995
1996     assert(fd->bytesRemain == -1);      /* XXX FIXME */
1997     gzfile = gzdFileno(fd);
1998     fdstat_enter(fd, FDSTAT_SEEK);
1999     rc = gzseek(gzfile, p, whence);
2000 DBGIO(fd, (stderr, "==>\tgzdSeek(%p,%ld,%d) rc %lx %s\n", cookie, (long)p, whence, (long)rc, fdbg(fd)));
2001     if (rc < 0) {
2002         int zerror = 0;
2003         fd->errcookie = gzerror(gzfile, &zerror);
2004         if (zerror == Z_ERRNO) {
2005             fd->syserrno = errno;
2006             fd->errcookie = strerror(fd->syserrno);
2007         }
2008     } else if (rc >= 0) {
2009         fdstat_exit(fd, FDSTAT_SEEK, rc);
2010     }
2011 #else
2012     rc = -2;
2013 #endif
2014     return rc;
2015 }
2016
2017 static int gzdClose( /*@only@*/ void * cookie) {
2018     FD_t fd = c2f(cookie);
2019     gzFile *gzfile;
2020     int rc;
2021
2022     gzfile = gzdFileno(fd);
2023
2024     if (gzfile == NULL) return -2;
2025     fdstat_enter(fd, FDSTAT_CLOSE);
2026     rc = gzclose(gzfile);
2027
2028     /* XXX TODO: preserve fd if errors */
2029
2030     if (fd) {
2031 DBGIO(fd, (stderr, "==>\tgzdClose(%p) zerror %d %s\n", cookie, rc, fdbg(fd)));
2032         if (rc < 0) {
2033             fd->errcookie = gzerror(gzfile, &rc);
2034             if (rc == Z_ERRNO) {
2035                 fd->syserrno = errno;
2036                 fd->errcookie = strerror(fd->syserrno);
2037             }
2038         } else if (rc >= 0) {
2039             fdstat_exit(fd, FDSTAT_CLOSE, rc);
2040         }
2041     }
2042
2043 DBGIO(fd, (stderr, "==>\tgzdClose(%p) rc %lx %s\n", cookie, (long)rc, fdbg(fd)));
2044
2045     if (_rpmio_debug || rpmIsDebug()) fdstat_print(fd, "GZDIO", stderr);
2046     if (rc == 0)
2047         fd = fdFree(fd, "open (gzdClose)");
2048     return rc;
2049 }
2050
2051 static struct FDIO_s gzdio_s = {
2052   gzdRead, gzdWrite, gzdSeek, gzdClose, XfdLink, XfdFree, XfdNew, fdFileno,
2053   NULL, gzdOpen, gzdFileno, gzdFlush,   NULL, NULL, NULL, NULL, NULL
2054 };
2055 FDIO_t gzdio = /*@-compmempass@*/ &gzdio_s /*@=compmempass@*/ ;
2056
2057 #endif  /* HAVE_ZLIB_H */
2058
2059 /* =============================================================== */
2060 /* Support for BZIP2 library.
2061  */
2062 #if HAVE_BZLIB_H
2063
2064 #include <bzlib.h>
2065
2066 #ifdef HAVE_BZ2_1_0
2067 # define bzopen  BZ2_bzopen
2068 # define bzclose BZ2_bzclose
2069 # define bzdopen BZ2_bzdopen
2070 # define bzerror BZ2_bzerror
2071 # define bzflush BZ2_bzflush
2072 # define bzread  BZ2_bzread
2073 # define bzwrite BZ2_bzwrite
2074 #endif /* HAVE_BZ2_1_0 */
2075
2076 static inline /*@dependent@*/ /*@null@*/ void * bzdFileno(FD_t fd) {
2077     void * rc = NULL;
2078     int i;
2079
2080     FDSANE(fd);
2081     for (i = fd->nfps; i >= 0; i--) {
2082         FDSTACK_t * fps = &fd->fps[i];
2083         if (fps->io != bzdio)
2084             continue;
2085         rc = fps->fp;
2086         break;
2087     }
2088     
2089     return rc;
2090 }
2091
2092 static /*@null@*/ FD_t bzdOpen(const char *path, const char *mode) {
2093     FD_t fd;
2094     BZFILE *bzfile;;
2095     if ((bzfile = bzopen(path, mode)) == NULL)
2096         return NULL;
2097     fd = fdNew("open (bzdOpen)");
2098     fdPop(fd); fdPush(fd, bzdio, bzfile, -1);
2099     return fdLink(fd, "bzdOpen");
2100 }
2101
2102 static /*@null@*/ FD_t bzdFdopen(void * cookie, const char * fmode) {
2103     FD_t fd = c2f(cookie);
2104     int fdno;
2105     BZFILE *bzfile;
2106
2107     if (fmode == NULL) return NULL;
2108     fdno = fdFileno(fd);
2109     fdSetFdno(fd, -1);          /* XXX skip the fdio close */
2110     if (fdno < 0) return NULL;
2111     bzfile = bzdopen(fdno, fmode);
2112     if (bzfile == NULL) return NULL;
2113
2114     fdPush(fd, bzdio, bzfile, fdno);            /* Push bzdio onto stack */
2115
2116     return fdLink(fd, "bzdFdopen");
2117 }
2118
2119 static int bzdFlush(FD_t fd) {
2120     return bzflush(bzdFileno(fd));
2121 }
2122
2123 /* =============================================================== */
2124 static ssize_t bzdRead(void * cookie, /*@out@*/ char * buf, size_t count) {
2125     FD_t fd = c2f(cookie);
2126     BZFILE *bzfile;
2127     ssize_t rc;
2128
2129     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
2130     bzfile = bzdFileno(fd);
2131     fdstat_enter(fd, FDSTAT_READ);
2132     rc = bzread(bzfile, buf, count);
2133     if (rc == -1) {
2134         int zerror = 0;
2135         fd->errcookie = bzerror(bzfile, &zerror);
2136     } else if (rc >= 0) {
2137         fdstat_exit(fd, FDSTAT_READ, rc);
2138     }
2139     return rc;
2140 }
2141
2142 static ssize_t bzdWrite(void * cookie, const char * buf, size_t count) {
2143     FD_t fd = c2f(cookie);
2144     BZFILE *bzfile;
2145     ssize_t rc;
2146
2147     if (fd->bytesRemain == 0) return 0; /* XXX simulate EOF */
2148     bzfile = bzdFileno(fd);
2149     fdstat_enter(fd, FDSTAT_WRITE);
2150     rc = bzwrite(bzfile, (void *)buf, count);
2151     if (rc == -1) {
2152         int zerror = 0;
2153         fd->errcookie = bzerror(bzfile, &zerror);
2154     } else if (rc > 0) {
2155         fdstat_exit(fd, FDSTAT_WRITE, rc);
2156     }
2157     return rc;
2158 }
2159
2160 #ifdef USE_COOKIE_SEEK_POINTER
2161 static inline int bzdSeek(void * cookie, _IO_off64_t *pos, int whence) {
2162 #else
2163 static inline int bzdSeek(void * cookie, off_t p, int whence) {
2164 #endif
2165     FD_t fd = c2f(cookie);
2166
2167     BZDONLY(fd);
2168     return -2;
2169 }
2170
2171 static int bzdClose( /*@only@*/ void * cookie) {
2172     FD_t fd = c2f(cookie);
2173     BZFILE *bzfile;
2174     int rc;
2175
2176     bzfile = bzdFileno(fd);
2177
2178     if (bzfile == NULL) return -2;
2179     fdstat_enter(fd, FDSTAT_CLOSE);
2180     bzclose(bzfile);
2181     rc = 0;     /* XXX FIXME */
2182
2183     /* XXX TODO: preserve fd if errors */
2184
2185     if (fd) {
2186         if (rc == -1) {
2187             int zerror = 0;
2188             fd->errcookie = bzerror(bzfile, &zerror);
2189         } else if (rc >= 0) {
2190             fdstat_exit(fd, FDSTAT_CLOSE, rc);
2191         }
2192     }
2193
2194 DBGIO(fd, (stderr, "==>\tbzdClose(%p) rc %lx %s\n", cookie, (long)rc, fdbg(fd)));
2195
2196     if (_rpmio_debug || rpmIsDebug()) fdstat_print(fd, "BZDIO", stderr);
2197     if (rc == 0)
2198         fd = fdFree(fd, "open (bzdClose)");
2199     return rc;
2200 }
2201
2202 static struct FDIO_s bzdio_s = {
2203   bzdRead, bzdWrite, bzdSeek, bzdClose, XfdLink, XfdFree, XfdNew, fdFileno,
2204   NULL, bzdOpen, bzdFileno, bzdFlush,   NULL, NULL, NULL, NULL, NULL
2205 };
2206 FDIO_t bzdio = /*@-compmempass@*/ &bzdio_s /*@=compmempass@*/ ;
2207
2208 #endif  /* HAVE_BZLIB_H */
2209
2210 /* =============================================================== */
2211 /*@observer@*/ static const char * getFdErrstr (FD_t fd) {
2212     const char *errstr = NULL;
2213
2214 #ifdef  HAVE_ZLIB_H
2215     if (fdGetIo(fd) == gzdio) {
2216         errstr = fd->errcookie;
2217     } else
2218 #endif  /* HAVE_ZLIB_H */
2219
2220 #ifdef  HAVE_BZLIB_H
2221     if (fdGetIo(fd) == bzdio) {
2222         errstr = fd->errcookie;
2223     } else
2224 #endif  /* HAVE_BZLIB_H */
2225
2226     {
2227         errstr = strerror(fd->syserrno);
2228     }
2229
2230     return errstr;
2231 }
2232
2233 /* =============================================================== */
2234
2235 const char *Fstrerror(FD_t fd) {
2236     if (fd == NULL)
2237         return strerror(errno);
2238     FDSANE(fd);
2239     return getFdErrstr(fd);
2240 }
2241
2242 #define FDIOVEC(_fd, _vec)      \
2243   ((fdGetIo(_fd) && fdGetIo(_fd)->_vec) ? fdGetIo(_fd)->_vec : NULL)
2244
2245 size_t Fread(void *buf, size_t size, size_t nmemb, FD_t fd) {
2246     fdio_read_function_t *_read;
2247     int rc;
2248
2249     FDSANE(fd);
2250 #ifdef __LCLINT__
2251     *(char *)buf = '\0';
2252 #endif
2253 DBGIO(fd, (stderr, "==> Fread(%p,%u,%u,%p) %s\n", buf, (unsigned)size, (unsigned)nmemb, fd, fdbg(fd)));
2254
2255     if (fdGetIo(fd) == fpio) {
2256         rc = fread(buf, size, nmemb, fdGetFILE(fd));
2257         return rc;
2258     }
2259
2260     _read = FDIOVEC(fd, read);
2261
2262     rc = (_read ? (*_read) (fd, buf, size * nmemb) : -2);
2263     return rc;
2264 }
2265
2266 size_t Fwrite(const void *buf, size_t size, size_t nmemb, FD_t fd) {
2267     fdio_write_function_t *_write;
2268     int rc;
2269
2270     FDSANE(fd);
2271 DBGIO(fd, (stderr, "==> Fwrite(%p,%u,%u,%p) %s\n", buf, (unsigned)size, (unsigned)nmemb, fd, fdbg(fd)));
2272
2273     if (fdGetIo(fd) == fpio) {
2274         rc = fwrite(buf, size, nmemb, fdGetFILE(fd));
2275         return rc;
2276     }
2277
2278     _write = FDIOVEC(fd, write);
2279
2280     rc = (_write ? _write(fd, buf, size * nmemb) : -2);
2281     return rc;
2282 }
2283
2284 #ifdef USE_COOKIE_SEEK_POINTER
2285 int Fseek(FD_t fd, _IO_off64_t offset, int whence) {
2286 #else 
2287 int Fseek(FD_t fd, off_t offset, int whence) {
2288 #endif
2289     fdio_seek_function_t *_seek;
2290     long int rc;
2291
2292     FDSANE(fd);
2293 DBGIO(fd, (stderr, "==> Fseek(%p,%ld,%d) %s\n", fd, (long)offset, whence, fdbg(fd)));
2294
2295     if (fdGetIo(fd) == fpio) {
2296         FILE *f;
2297
2298         f = fdGetFILE(fd);
2299         rc = fseek(f, offset, whence);
2300         return rc;
2301     }
2302
2303     _seek = FDIOVEC(fd, seek);
2304
2305 #ifdef USE_COOKIE_SEEK_POINTER
2306     rc = (_seek ? _seek(fd, &offset, whence) : -2);
2307 #else
2308     rc = (_seek ? _seek(fd, offset, whence) : -2);
2309 #endif
2310     return rc;
2311 }
2312
2313 int Fclose(FD_t fd) {
2314     int rc, ec = 0;
2315
2316     FDSANE(fd);
2317 DBGIO(fd, (stderr, "==> Fclose(%p) %s\n", fd, fdbg(fd)));
2318
2319     fd = fdLink(fd, "Fclose");
2320     while (fd->nfps >= 0) {
2321         FDSTACK_t * fps = &fd->fps[fd->nfps];
2322         
2323         if (fps->io == fpio) {
2324             FILE *fp = fdGetFILE(fd);
2325             int fpno = fileno(fp);
2326
2327         /* XXX persistent HTTP/1.1 returns the previously opened fp */
2328             if (fd->nfps > 0 && fpno == -1 &&
2329                 fd->fps[fd->nfps-1].io == ufdio &&
2330                 fd->fps[fd->nfps-1].fp == fp &&
2331                 fd->fps[fd->nfps-1].fdno >= 0)
2332             {
2333                 fflush(fp);
2334                 fd->nfps--;
2335                 rc = ufdClose(fd);
2336                 if (fdGetFdno(fd) >= 0)
2337                     break;
2338                 fdSetFp(fd, NULL);
2339                 fd->nfps++;
2340                 rc = fclose(fp);
2341                 fdPop(fd);
2342                 if (noLibio)
2343                     fdSetFp(fd, NULL);
2344             } else {
2345                 rc = fclose(fp);
2346                 if (fpno == -1) {
2347                     fd = fdFree(fd, "fopencookie (Fclose)");
2348                     fdPop(fd);
2349                 }
2350             }
2351         } else {
2352             fdio_close_function_t * _close = FDIOVEC(fd, close);
2353             rc = _close(fd);
2354         }
2355         if (fd->nfps == 0)
2356             break;
2357         if (ec == 0 && rc)
2358             ec = rc;
2359         fdPop(fd);
2360     }
2361     fd = fdFree(fd, "Fclose");
2362     return ec;
2363 }
2364
2365 /*
2366  * Convert stdio fmode to open(2) mode, filtering out zlib/bzlib flags.
2367  *      returns stdio[0] = '\0' on error.
2368  *
2369  * gzopen:      [0-9] is compession level
2370  * gzopen:      'f' is filtered (Z_FILTERED)
2371  * gzopen:      'h' is Huffman encoding (Z_HUFFMAN_ONLY)
2372  * bzopen:      [1-9] is block size (modulo 100K)
2373  * bzopen:      's' is smallmode
2374  * HACK:        '.' terminates, rest is type of I/O
2375  */
2376 static inline void cvtfmode (const char *m,
2377                                 /*@out@*/ char *stdio, size_t nstdio,
2378                                 /*@out@*/ char *other, size_t nother,
2379                                 /*@out@*/ const char **end, /*@out@*/ int * f)
2380 {
2381     int flags = 0;
2382     char c;
2383
2384     switch (*m) {
2385     case 'a':
2386         flags |= O_WRONLY | O_CREAT | O_APPEND;
2387         if (--nstdio > 0) *stdio++ = *m;
2388         break;
2389     case 'w':
2390         flags |= O_WRONLY | O_CREAT | O_TRUNC;
2391         if (--nstdio > 0) *stdio++ = *m;
2392         break;
2393     case 'r':
2394         flags |= O_RDONLY;
2395         if (--nstdio > 0) *stdio++ = *m;
2396         break;
2397     default:
2398         *stdio = '\0';
2399         return;
2400         /*@notreached@*/ break;
2401     }
2402     m++;
2403
2404     while ((c = *m++) != '\0') {
2405         switch (c) {
2406         case '.':
2407             break;
2408         case '+':
2409             flags &= ~(O_RDONLY|O_WRONLY);
2410             flags |= O_RDWR;
2411             if (--nstdio > 0) *stdio++ = c;
2412             continue;
2413         case 'b':
2414             if (--nstdio > 0) *stdio++ = c;
2415             continue;
2416         case 'x':
2417             flags |= O_EXCL;
2418             if (--nstdio > 0) *stdio++ = c;
2419             continue;
2420         default:
2421             if (--nother > 0) *other++ = c;
2422             continue;
2423         }
2424         break;
2425     }
2426
2427     *stdio = *other = '\0';
2428     if (end)
2429         *end = (*m ? m : NULL);
2430     if (f)
2431         *f = flags;
2432 }
2433
2434 #if _USE_LIBIO
2435 #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 0
2436 /* XXX retrofit glibc-2.1.x typedef on glibc-2.0.x systems */
2437 typedef _IO_cookie_io_functions_t cookie_io_functions_t;
2438 #endif
2439 #endif
2440
2441 FD_t Fdopen(FD_t ofd, const char *fmode)
2442 {
2443     char stdio[20], other[20], zstdio[20];
2444     const char *end = NULL;
2445     FDIO_t iof = NULL;
2446     FD_t fd = ofd;
2447
2448 if (_rpmio_debug)
2449 fprintf(stderr, "*** Fdopen(%p,%s) %s\n", fd, fmode, fdbg(fd));
2450     FDSANE(fd);
2451
2452     if (fmode == NULL)
2453         return NULL;
2454
2455     cvtfmode(fmode, stdio, sizeof(stdio), other, sizeof(other), &end, NULL);
2456     if (stdio[0] == '\0')
2457         return NULL;
2458     zstdio[0] = '\0';
2459     strncat(zstdio, stdio, sizeof(zstdio) - strlen(zstdio));
2460     strncat(zstdio, other, sizeof(zstdio) - strlen(zstdio));
2461
2462     if (end == NULL && other[0] == '\0')
2463         return fd;
2464
2465     if (end && *end) {
2466         if (!strcmp(end, "fdio")) {
2467             iof = fdio;
2468         } else if (!strcmp(end, "gzdio")) {
2469             iof = gzdio;
2470             fd = gzdFdopen(fd, zstdio);
2471 #if HAVE_BZLIB_H
2472         } else if (!strcmp(end, "bzdio")) {
2473             iof = bzdio;
2474             fd = bzdFdopen(fd, zstdio);
2475 #endif
2476         } else if (!strcmp(end, "ufdio")) {
2477             iof = ufdio;
2478         } else if (!strcmp(end, "fadio")) {
2479             iof = fadio;
2480         } else if (!strcmp(end, "fpio")) {
2481             iof = fpio;
2482             if (noLibio) {
2483                 int fdno = Fileno(fd);
2484                 FILE * fp = fdopen(fdno, stdio);
2485 if (_rpmio_debug)
2486 fprintf(stderr, "*** Fdopen fpio fp %p\n", fp);
2487                 if (fp == NULL)
2488                     return NULL;
2489                 /* XXX gzdio/bzdio use fp for private data */
2490                 if (fdGetFp(fd) == NULL)
2491                     fdSetFp(fd, fp);
2492                 fdPush(fd, fpio, fp, fdno);     /* Push fpio onto stack */
2493             }
2494         }
2495     } else if (other[0]) {
2496         for (end = other; *end && strchr("0123456789fh", *end); end++)
2497             ;
2498         if (*end == '\0') {
2499             iof = gzdio;
2500             fd = gzdFdopen(fd, zstdio);
2501         }
2502     }
2503     if (iof == NULL)
2504         return fd;
2505
2506     if (!noLibio) {
2507         FILE * fp = NULL;
2508
2509 #if _USE_LIBIO
2510         {   cookie_io_functions_t ciof;
2511             ciof.read = iof->read;
2512             ciof.write = iof->write;
2513             ciof.seek = iof->seek;
2514             ciof.close = iof->close;
2515             fp = fopencookie(fd, stdio, ciof);
2516 DBGIO(fd, (stderr, "==> fopencookie(%p,\"%s\",*%p) returns fp %p\n", fd, stdio, iof, fp));
2517         }
2518 #endif
2519
2520         if (fp) {
2521             /* XXX gzdio/bzdio use fp for private data */
2522             if (fdGetFp(fd) == NULL)
2523                 fdSetFp(fd, fp);
2524             fdPush(fd, fpio, fp, fileno(fp));   /* Push fpio onto stack */
2525             fd = fdLink(fd, "fopencookie");
2526         }
2527     }
2528
2529 DBGIO(fd, (stderr, "==> Fdopen(%p,\"%s\") returns fd %p %s\n", ofd, fmode, fd, fdbg(fd)));
2530     return fd;
2531 }
2532
2533 FD_t Fopen(const char *path, const char *fmode)
2534 {
2535     char stdio[20], other[20];
2536     const char *end = NULL;
2537     mode_t perms = 0666;
2538     int flags;
2539     FD_t fd;
2540
2541     if (path == NULL || fmode == NULL)
2542         return NULL;
2543
2544     cvtfmode(fmode, stdio, sizeof(stdio), other, sizeof(other), &end, &flags);
2545     if (stdio[0] == '\0')
2546         return NULL;
2547
2548     if (end == NULL || !strcmp(end, "fdio")) {
2549 if (_rpmio_debug)
2550 fprintf(stderr, "*** Fopen fdio path %s fmode %s\n", path, fmode);
2551         fd = fdOpen(path, flags, perms);
2552         if (fdFileno(fd) < 0) {
2553             fdClose(fd);
2554             return NULL;
2555         }
2556     } else if (!strcmp(end, "fadio")) {
2557 if (_rpmio_debug)
2558 fprintf(stderr, "*** Fopen fadio path %s fmode %s\n", path, fmode);
2559         fd = fadio->_open(path, flags, perms);
2560         if (fdFileno(fd) < 0) {
2561             fdClose(fd);
2562             return NULL;
2563         }
2564     } else {
2565         FILE *fp;
2566         int fdno;
2567         int isHTTP = 0;
2568
2569         /* XXX gzdio and bzdio here too */
2570
2571         switch (urlIsURL(path)) {
2572         case URL_IS_HTTP:
2573             isHTTP = 1;
2574             /*@fallthrough@*/
2575         case URL_IS_PATH:
2576         case URL_IS_DASH:
2577         case URL_IS_FTP:
2578         case URL_IS_UNKNOWN:
2579 if (_rpmio_debug)
2580 fprintf(stderr, "*** Fopen ufdio path %s fmode %s\n", path, fmode);
2581             fd = ufdOpen(path, flags, perms);
2582             if (fd == NULL || fdFileno(fd) < 0)
2583                 return fd;
2584             break;
2585         default:
2586 if (_rpmio_debug)
2587 fprintf(stderr, "*** Fopen WTFO path %s fmode %s\n", path, fmode);
2588             return NULL;
2589             /*@notreached@*/ break;
2590         }
2591
2592         /* XXX persistent HTTP/1.1 returns the previously opened fp */
2593         if (isHTTP && ((fp = fdGetFp(fd)) != NULL) && ((fdno = fdGetFdno(fd)) >= 0)) {
2594             fdPush(fd, fpio, fp, fileno(fp));   /* Push fpio onto stack */
2595             return fd;
2596         }
2597     }
2598
2599     fd = Fdopen(fd, fmode);
2600     return fd;
2601 }
2602
2603 int Fflush(FD_t fd)
2604 {
2605     if (fd == NULL) return -1;
2606     if (fdGetIo(fd) == fpio)
2607         return fflush(fdGetFILE(fd));
2608     if (fdGetIo(fd) == gzdio)
2609         return gzdFlush(fdGetFp(fd));
2610 #if HAVE_BZLIB_H
2611     if (fdGetIo(fd) == bzdio)
2612         return bzdFlush(fdGetFp(fd));
2613 #endif
2614     return 0;
2615 }
2616
2617 int Ferror(FD_t fd) {
2618     int i, rc = 0;
2619
2620     if (fd == NULL) return -1;
2621     for (i = fd->nfps; rc == 0 && i >= 0; i--) {
2622         FDSTACK_t * fps = &fd->fps[i];
2623         int ec;
2624         
2625         if (fps->io == fpio) {
2626             ec = ferror(fdGetFILE(fd));
2627         } else if (fps->io == gzdio) {
2628             ec = (fd->syserrno  || fd->errcookie != NULL) ? -1 : 0;
2629 #if HAVE_BZLIB_H
2630         } else if (fps->io == bzdio) {
2631             ec = (fd->syserrno  || fd->errcookie != NULL) ? -1 : 0;
2632 #endif
2633         } else {
2634         /* XXX need to check ufdio/gzdio/bzdio/fdio errors correctly. */
2635             ec = (fdFileno(fd) < 0 ? -1 : 0);
2636         }
2637
2638         if (rc == 0 && ec)
2639             rc = ec;
2640     }
2641 DBGIO(fd, (stderr, "==> Ferror(%p) rc %d %s\n", fd, rc, fdbg(fd)));
2642     return rc;
2643 }
2644
2645 int Fileno(FD_t fd) {
2646     int i, rc = -1;
2647
2648     for (i = fd->nfps ; rc == -1 && i >= 0; i--) {
2649         rc = fd->fps[i].fdno;
2650     }
2651 DBGIO(fd, (stderr, "==> Fileno(%p) rc %d %s\n", fd, rc, fdbg(fd)));
2652     return rc;
2653 }
2654
2655 /* XXX this is naive */
2656 int Fcntl(FD_t fd, int op, void *lip) {
2657     return fcntl(Fileno(fd), op, lip);
2658 }
2659
2660 /* =============================================================== */
2661 /* Helper routines that may be generally useful.
2662  */
2663
2664 /* XXX falloc.c: analogues to pread(3)/pwrite(3). */
2665 #ifdef USE_COOKIE_SEEK_POINTER
2666 ssize_t Pread(FD_t fd, void * buf, size_t count, _IO_off64_t offset) {
2667 #else
2668 ssize_t Pread(FD_t fd, void * buf, size_t count, off_t offset) {
2669 #endif
2670     if (Fseek(fd, offset, SEEK_SET) < 0)
2671         return -1;
2672     return Fread(buf, sizeof(char), count, fd);
2673 }
2674
2675 #ifdef USE_COOKIE_SEEK_POINTER
2676 ssize_t Pwrite(FD_t fd, const void * buf, size_t count, _IO_off64_t offset) {
2677 #else
2678 ssize_t Pwrite(FD_t fd, const void * buf, size_t count, off_t offset) {
2679 #endif
2680     if (Fseek(fd, offset, SEEK_SET) < 0)
2681         return -1;
2682     return Fwrite(buf, sizeof(char), count, fd);
2683 }
2684
2685 /* XXX rebuilddb.c: analogues to mkdir(2)/rmdir(2). */
2686 int Mkdir (const char *path, mode_t mode) {
2687     const char * lpath;
2688     int ut = urlPath(path, &lpath);
2689
2690     switch (ut) {
2691     case URL_IS_FTP:
2692         return ftpMkdir(path, mode);
2693         /*@notreached@*/ break;
2694     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2695     case URL_IS_PATH:
2696         path = lpath;
2697         /*@fallthrough@*/
2698     case URL_IS_UNKNOWN:
2699         break;
2700     case URL_IS_DASH:
2701     default:
2702         return -2;
2703         /*@notreached@*/ break;
2704     }
2705     return mkdir(path, mode);
2706 }
2707
2708 int Chdir (const char *path) {
2709     const char * lpath;
2710     int ut = urlPath(path, &lpath);
2711
2712     switch (ut) {
2713     case URL_IS_FTP:
2714         return ftpChdir(path);
2715         /*@notreached@*/ break;
2716     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2717     case URL_IS_PATH:
2718         path = lpath;
2719         /*@fallthrough@*/
2720     case URL_IS_UNKNOWN:
2721         break;
2722     case URL_IS_DASH:
2723     default:
2724         return -2;
2725         /*@notreached@*/ break;
2726     }
2727     return chdir(path);
2728 }
2729
2730 int Rmdir (const char *path) {
2731     const char * lpath;
2732     int ut = urlPath(path, &lpath);
2733
2734     switch (ut) {
2735     case URL_IS_FTP:
2736         return ftpRmdir(path);
2737         /*@notreached@*/ break;
2738     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2739     case URL_IS_PATH:
2740         path = lpath;
2741         /*@fallthrough@*/
2742     case URL_IS_UNKNOWN:
2743         break;
2744     case URL_IS_DASH:
2745     default:
2746         return -2;
2747         /*@notreached@*/ break;
2748     }
2749     return rmdir(path);
2750 }
2751
2752 /* XXX rpmdb.c: analogue to rename(2). */
2753
2754 int Rename (const char *oldpath, const char * newpath) {
2755     const char *oe = NULL;
2756     const char *ne = NULL;
2757     int oldut, newut;
2758
2759     /* XXX lib/install.c used to rely on this behavior. */
2760     if (!strcmp(oldpath, newpath)) return 0;
2761
2762     oldut = urlPath(oldpath, &oe);
2763     switch (oldut) {
2764     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
2765     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2766     case URL_IS_PATH:
2767     case URL_IS_UNKNOWN:
2768         break;
2769     case URL_IS_DASH:
2770     default:
2771         return -2;
2772         /*@notreached@*/ break;
2773     }
2774
2775     newut = urlPath(newpath, &ne);
2776     switch (newut) {
2777     case URL_IS_FTP:
2778 if (_rpmio_debug)
2779 fprintf(stderr, "*** rename old %*s new %*s\n", (int)(oe - oldpath), oldpath, (int)(ne - newpath), newpath);
2780         if (!(oldut == newut && oe && ne && (oe - oldpath) == (ne - newpath) &&
2781             !strncasecmp(oldpath, newpath, (oe - oldpath))))
2782             return -2;
2783         return ftpRename(oldpath, newpath);
2784         /*@notreached@*/ break;
2785     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2786     case URL_IS_PATH:
2787         oldpath = oe;
2788         newpath = ne;
2789         break;
2790     case URL_IS_UNKNOWN:
2791         break;
2792     case URL_IS_DASH:
2793     default:
2794         return -2;
2795         /*@notreached@*/ break;
2796     }
2797     return rename(oldpath, newpath);
2798 }
2799
2800 int Link (const char *oldpath, const char * newpath) {
2801     const char *oe = NULL;
2802     const char *ne = NULL;
2803     int oldut, newut;
2804
2805     oldut = urlPath(oldpath, &oe);
2806     switch (oldut) {
2807     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
2808     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2809     case URL_IS_PATH:
2810     case URL_IS_UNKNOWN:
2811         break;
2812     case URL_IS_DASH:
2813     default:
2814         return -2;
2815         /*@notreached@*/ break;
2816     }
2817
2818     newut = urlPath(newpath, &ne);
2819     switch (newut) {
2820     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2821     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
2822     case URL_IS_PATH:
2823 if (_rpmio_debug)
2824 fprintf(stderr, "*** link old %*s new %*s\n", (int)(oe - oldpath), oldpath, (int)(ne - newpath), newpath);
2825         if (!(oldut == newut && oe && ne && (oe - oldpath) == (ne - newpath) &&
2826             !strncasecmp(oldpath, newpath, (oe - oldpath))))
2827             return -2;
2828         oldpath = oe;
2829         newpath = ne;
2830         break;
2831     case URL_IS_UNKNOWN:
2832         break;
2833     case URL_IS_DASH:
2834     default:
2835         return -2;
2836         /*@notreached@*/ break;
2837     }
2838     return link(oldpath, newpath);
2839 }
2840
2841 /* XXX build/build.c: analogue to unlink(2). */
2842
2843 int Unlink(const char * path) {
2844     const char * lpath;
2845     int ut = urlPath(path, &lpath);
2846
2847     switch (ut) {
2848     case URL_IS_FTP:
2849         return ftpUnlink(path);
2850         /*@notreached@*/ break;
2851     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
2852     case URL_IS_PATH:
2853         path = lpath;
2854         /*@fallthrough@*/
2855     case URL_IS_UNKNOWN:
2856         break;
2857     case URL_IS_DASH:
2858     default:
2859         return -2;
2860         /*@notreached@*/ break;
2861     }
2862     return unlink(path);
2863 }
2864
2865 /* XXX swiped from mc-4.5.39-pre9 vfs/ftpfs.c */
2866
2867 #define g_strdup        xstrdup
2868 #define g_free          xfree
2869
2870 /*
2871  * FIXME: this is broken. It depends on mc not crossing border on month!
2872  */
2873 static int current_mday;
2874 static int current_mon;
2875 static int current_year;
2876
2877 /* Following stuff (parse_ls_lga) is used by ftpfs and extfs */
2878 #define MAXCOLS         30
2879
2880 static char *columns [MAXCOLS]; /* Points to the string in column n */
2881 static int   column_ptr [MAXCOLS]; /* Index from 0 to the starting positions of the columns */
2882
2883 static int
2884 vfs_split_text (char *p)
2885 {
2886     char *original = p;
2887     int  numcols;
2888
2889
2890     for (numcols = 0; *p && numcols < MAXCOLS; numcols++){
2891         while (*p == ' ' || *p == '\r' || *p == '\n'){
2892             *p = 0;
2893             p++;
2894         }
2895         columns [numcols] = p;
2896         column_ptr [numcols] = p - original;
2897         while (*p && *p != ' ' && *p != '\r' && *p != '\n')
2898             p++;
2899     }
2900     return numcols;
2901 }
2902
2903 static int
2904 is_num (int idx)
2905 {
2906     if (!columns [idx] || columns [idx][0] < '0' || columns [idx][0] > '9')
2907         return 0;
2908     return 1;
2909 }
2910
2911 static int
2912 is_dos_date(char *str)
2913 {
2914     if (strlen(str) == 8 && str[2] == str[5] && strchr("\\-/", (int)str[2]) != NULL)
2915         return (1);
2916
2917     return (0);
2918 }
2919
2920 static int
2921 is_week (char *str, struct tm *tim)
2922 {
2923     static char *week = "SunMonTueWedThuFriSat";
2924     char *pos;
2925
2926     if((pos=strstr(week, str)) != NULL){
2927         if(tim != NULL)
2928             tim->tm_wday = (pos - week)/3;
2929         return (1);
2930     }
2931     return (0);    
2932 }
2933
2934 static int
2935 is_month (char *str, struct tm *tim)
2936 {
2937     static char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
2938     char *pos;
2939     
2940     if((pos=strstr(month, str)) != NULL){
2941         if(tim != NULL)
2942             tim->tm_mon = (pos - month)/3;
2943         return (1);
2944     }
2945     return (0);
2946 }
2947
2948 static int
2949 is_time (char *str, struct tm *tim)
2950 {
2951     char *p, *p2;
2952
2953     if ((p=strchr(str, ':')) && (p2=strrchr(str, ':'))) {
2954         if (p != p2) {
2955             if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
2956                 return (0);
2957         }
2958         else {
2959             if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
2960                 return (0);
2961         }
2962     }
2963     else 
2964         return (0);
2965     
2966     return (1);
2967 }
2968
2969 static int is_year(char *str, struct tm *tim)
2970 {
2971     long year;
2972
2973     if (strchr(str,':'))
2974         return (0);
2975
2976     if (strlen(str)!=4)
2977         return (0);
2978
2979     if (sscanf(str, "%ld", &year) != 1)
2980         return (0);
2981
2982     if (year < 1900 || year > 3000)
2983         return (0);
2984
2985     tim->tm_year = (int) (year - 1900);
2986
2987     return (1);
2988 }
2989
2990 /*
2991  * FIXME: this is broken. Consider following entry:
2992  * -rwx------   1 root     root            1 Aug 31 10:04 2904 1234
2993  * where "2904 1234" is filename. Well, this code decodes it as year :-(.
2994  */
2995
2996 static int
2997 vfs_parse_filetype (char c)
2998 {
2999     switch (c){
3000         case 'd': return S_IFDIR; 
3001         case 'b': return S_IFBLK;
3002         case 'c': return S_IFCHR;
3003         case 'l': return S_IFLNK;
3004         case 's':
3005 #ifdef IS_IFSOCK /* And if not, we fall through to IFIFO, which is pretty close */
3006                   return S_IFSOCK;
3007 #endif
3008         case 'p': return S_IFIFO;
3009         case 'm': case 'n':             /* Don't know what these are :-) */
3010         case '-': case '?': return S_IFREG;
3011         default: return -1;
3012     }
3013 }
3014
3015 static int vfs_parse_filemode (char *p)
3016 {       /* converts rw-rw-rw- into 0666 */
3017     int res = 0;
3018     switch (*(p++)){
3019         case 'r': res |= 0400; break;
3020         case '-': break;
3021         default: return -1;
3022     }
3023     switch (*(p++)){
3024         case 'w': res |= 0200; break;
3025         case '-': break;
3026         default: return -1;
3027     }
3028     switch (*(p++)){
3029         case 'x': res |= 0100; break;
3030         case 's': res |= 0100 | S_ISUID; break;
3031         case 'S': res |= S_ISUID; break;
3032         case '-': break;
3033         default: return -1;
3034     }
3035     switch (*(p++)){
3036         case 'r': res |= 0040; break;
3037         case '-': break;
3038         default: return -1;
3039     }
3040     switch (*(p++)){
3041         case 'w': res |= 0020; break;
3042         case '-': break;
3043         default: return -1;
3044     }
3045     switch (*(p++)){
3046         case 'x': res |= 0010; break;
3047         case 's': res |= 0010 | S_ISGID; break;
3048         case 'l': /* Solaris produces these */
3049         case 'S': res |= S_ISGID; break;
3050         case '-': break;
3051         default: return -1;
3052     }
3053     switch (*(p++)){
3054         case 'r': res |= 0004; break;
3055         case '-': break;
3056         default: return -1;
3057     }
3058     switch (*(p++)){
3059         case 'w': res |= 0002; break;
3060         case '-': break;
3061         default: return -1;
3062     }
3063     switch (*(p++)){
3064         case 'x': res |= 0001; break;
3065         case 't': res |= 0001 | S_ISVTX; break;
3066         case 'T': res |= S_ISVTX; break;
3067         case '-': break;
3068         default: return -1;
3069     }
3070     return res;
3071 }
3072
3073 static int vfs_parse_filedate(int idx, time_t *t)
3074 {       /* This thing parses from idx in columns[] array */
3075
3076     char *p;
3077     struct tm tim;
3078     int d[3];
3079     int got_year = 0;
3080
3081     /* Let's setup default time values */
3082     tim.tm_year = current_year;
3083     tim.tm_mon  = current_mon;
3084     tim.tm_mday = current_mday;
3085     tim.tm_hour = 0;
3086     tim.tm_min  = 0;
3087     tim.tm_sec  = 0;
3088     tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
3089     
3090     p = columns [idx++];
3091     
3092     /* We eat weekday name in case of extfs */
3093     if(is_week(p, &tim))
3094         p = columns [idx++];
3095
3096     /* Month name */
3097     if(is_month(p, &tim)){
3098         /* And we expect, it followed by day number */
3099         if (is_num (idx))
3100             tim.tm_mday = (int)atol (columns [idx++]);
3101         else
3102             return 0; /* No day */
3103
3104     } else {
3105         /* We usually expect:
3106            Mon DD hh:mm
3107            Mon DD  YYYY
3108            But in case of extfs we allow these date formats:
3109            Mon DD YYYY hh:mm
3110            Mon DD hh:mm YYYY
3111            Wek Mon DD hh:mm:ss YYYY
3112            MM-DD-YY hh:mm
3113            where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
3114            YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
3115
3116         /* Here just this special case with MM-DD-YY */
3117         if (is_dos_date(p)){
3118             p[2] = p[5] = '-';
3119             
3120             if(sscanf(p, "%2d-%2d-%2d", &d[0], &d[1], &d[2]) == 3){
3121             /*  We expect to get:
3122                 1. MM-DD-YY
3123                 2. DD-MM-YY
3124                 3. YY-MM-DD
3125                 4. YY-DD-MM  */
3126                 
3127                 /* Hmm... maybe, next time :)*/
3128                 
3129                 /* At last, MM-DD-YY */
3130                 d[0]--; /* Months are zerobased */
3131                 /* Y2K madness */
3132                 if(d[2] < 70)
3133                     d[2] += 100;
3134
3135                 tim.tm_mon  = d[0];
3136                 tim.tm_mday = d[1];
3137                 tim.tm_year = d[2];
3138                 got_year = 1;
3139             } else
3140                 return 0; /* sscanf failed */
3141         } else
3142             return 0; /* unsupported format */
3143     }
3144
3145     /* Here we expect to find time and/or year */
3146     
3147     if (is_num (idx)) {
3148         if(is_time(columns[idx], &tim) || (got_year = is_year(columns[idx], &tim))) {
3149         idx++;
3150
3151         /* This is a special case for ctime() or Mon DD YYYY hh:mm */
3152         if(is_num (idx) && 
3153             ((got_year = is_year(columns[idx], &tim)) || is_time(columns[idx], &tim)))
3154                 idx++; /* time & year or reverse */
3155         } /* only time or date */
3156     }
3157     else 
3158         return 0; /* Nor time or date */
3159
3160     /*
3161      * If the date is less than 6 months in the past, it is shown without year
3162      * other dates in the past or future are shown with year but without time
3163      * This does not check for years before 1900 ... I don't know, how
3164      * to represent them at all
3165      */
3166     if (!got_year &&
3167         current_mon < 6 && current_mon < tim.tm_mon && 
3168         tim.tm_mon - current_mon >= 6)
3169
3170         tim.tm_year--;
3171
3172     if ((*t = mktime(&tim)) < 0)
3173         *t = 0;
3174     return idx;
3175 }
3176
3177 static int
3178 vfs_parse_ls_lga (char *p, struct stat *s, char **filename, char **linkname)
3179 {
3180     int idx, idx2, num_cols;
3181     int i;
3182     char *p_copy;
3183     
3184     if (strncmp (p, "total", 5) == 0)
3185         return 0;
3186
3187     p_copy = g_strdup(p);
3188 /* XXX FIXME: parse out inode number from "NLST -lai ." */
3189 /* XXX FIXME: parse out sizein blocks from "NLST -lais ." */
3190
3191     if ((i = vfs_parse_filetype(*(p++))) == -1)
3192         goto error;
3193
3194     s->st_mode = i;
3195     if (*p == ' ')      /* Notwell 4 */
3196         p++;
3197     if (*p == '['){
3198         if (strlen (p) <= 8 || p [8] != ']')
3199             goto error;
3200         /* Should parse here the Notwell permissions :) */
3201         if (S_ISDIR (s->st_mode))
3202             s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
3203         else
3204             s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
3205         p += 9;
3206     } else {
3207         if ((i = vfs_parse_filemode(p)) == -1)
3208             goto error;
3209         s->st_mode |= i;
3210         p += 9;
3211
3212         /* This is for an extra ACL attribute (HP-UX) */
3213         if (*p == '+')
3214             p++;
3215     }
3216
3217     g_free(p_copy);
3218     p_copy = g_strdup(p);
3219     num_cols = vfs_split_text (p);
3220
3221     s->st_nlink = atol (columns [0]);
3222     if (s->st_nlink < 0)
3223         goto error;
3224
3225     if (!is_num (1))
3226 #ifdef  HACK
3227         s->st_uid = finduid (columns [1]);
3228 #else
3229         unameToUid (columns [1], &s->st_uid);
3230 #endif
3231     else
3232         s->st_uid = (uid_t) atol (columns [1]);
3233
3234     /* Mhm, the ls -lg did not produce a group field */
3235     for (idx = 3; idx <= 5; idx++) 
3236         if (is_month(columns [idx], NULL) || is_week(columns [idx], NULL) || is_dos_date(columns[idx]))
3237             break;
3238
3239     if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
3240         goto error;
3241
3242     /* We don't have gid */     
3243     if (idx == 3 || (idx == 4 && (S_ISCHR(s->st_mode) || S_ISBLK (s->st_mode))))
3244         idx2 = 2;
3245     else { 
3246         /* We have gid field */
3247         if (is_num (2))
3248             s->st_gid = (gid_t) atol (columns [2]);
3249         else
3250 #ifdef  HACK
3251             s->st_gid = findgid (columns [2]);
3252 #else
3253             gnameToGid (columns [1], &s->st_gid);
3254 #endif
3255         idx2 = 3;
3256     }
3257
3258     /* This is device */
3259     if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode)){
3260         int maj, min;
3261         
3262         if (!is_num (idx2) || sscanf(columns [idx2], " %d,", &maj) != 1)
3263             goto error;
3264         
3265         if (!is_num (++idx2) || sscanf(columns [idx2], " %d", &min) != 1)
3266             goto error;
3267         
3268 #ifdef HAVE_ST_RDEV
3269         s->st_rdev = ((maj & 0xff) << 8) | (min & 0xffff00ff);
3270 #endif
3271         s->st_size = 0;
3272         
3273     } else {
3274         /* Common file size */
3275         if (!is_num (idx2))
3276             goto error;
3277         
3278         s->st_size = (size_t) atol (columns [idx2]);
3279 #ifdef HAVE_ST_RDEV
3280         s->st_rdev = 0;
3281 #endif
3282     }
3283
3284     idx = vfs_parse_filedate(idx, &s->st_mtime);
3285     if (!idx)
3286         goto error;
3287     /* Use resulting time value */
3288     s->st_atime = s->st_ctime = s->st_mtime;
3289     s->st_dev = 0;
3290     s->st_ino = 0;
3291 #ifdef HAVE_ST_BLKSIZE
3292     s->st_blksize = 512;
3293 #endif
3294 #ifdef HAVE_ST_BLOCKS
3295     s->st_blocks = (s->st_size + 511) / 512;
3296 #endif
3297
3298     for (i = idx + 1, idx2 = 0; i < num_cols; i++ ) 
3299         if (strcmp (columns [i], "->") == 0){
3300             idx2 = i;
3301             break;
3302         }
3303     
3304     if (((S_ISLNK (s->st_mode) || 
3305         (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
3306         && idx2){
3307         int p;
3308         char *s;
3309             
3310         if (filename){
3311 #ifdef HACK
3312             s = g_strndup (p_copy + column_ptr [idx], column_ptr [idx2] - column_ptr [idx] - 1);
3313 #else
3314             int nb = column_ptr [idx2] - column_ptr [idx] - 1;
3315             s = xmalloc(nb+1);
3316             strncpy(s, p_copy + column_ptr [idx], nb);
3317 #endif
3318             *filename = s;
3319         }
3320         if (linkname){
3321             s = g_strdup (p_copy + column_ptr [idx2+1]);
3322             p = strlen (s);
3323             if (s [p-1] == '\r' || s [p-1] == '\n')
3324                 s [p-1] = 0;
3325             if (s [p-2] == '\r' || s [p-2] == '\n')
3326                 s [p-2] = 0;
3327                 
3328             *linkname = s;
3329         }
3330     } else {
3331         /* Extract the filename from the string copy, not from the columns
3332          * this way we have a chance of entering hidden directories like ". ."
3333          */
3334         if (filename){
3335             /* 
3336             *filename = g_strdup (columns [idx++]);
3337             */
3338             int p;
3339             char *s;
3340             
3341             s = g_strdup (p_copy + column_ptr [idx++]);
3342             p = strlen (s);
3343             /* g_strchomp(); */
3344             if (s [p-1] == '\r' || s [p-1] == '\n')
3345                 s [p-1] = 0;
3346             if (s [p-2] == '\r' || s [p-2] == '\n')
3347                 s [p-2] = 0;
3348             
3349             *filename = s;
3350         }
3351         if (linkname)
3352             *linkname = NULL;
3353     }
3354     g_free (p_copy);
3355     return 1;
3356
3357 error:
3358 #ifdef  HACK
3359     {
3360       static int errorcount = 0;
3361
3362       if (++errorcount < 5) {
3363         message_1s (1, "Could not parse:", p_copy);
3364       } else if (errorcount == 5)
3365         message_1s (1, "More parsing errors will be ignored.", "(sorry)" );
3366     }
3367 #endif
3368
3369     if (p_copy != p)            /* Carefull! */
3370         g_free (p_copy);
3371     return 0;
3372 }
3373
3374 typedef enum {
3375         DO_FTP_STAT     = 1,
3376         DO_FTP_LSTAT    = 2,
3377         DO_FTP_READLINK = 3,
3378         DO_FTP_ACCESS   = 4,
3379         DO_FTP_GLOB     = 5
3380 } ftpSysCall_t;
3381 static size_t ftpBufAlloced = 0;
3382 static char * ftpBuf = NULL;
3383         
3384 #define alloca_strdup(_s)       strcpy(alloca(strlen(_s)+1), (_s))
3385
3386 static int ftpNLST(const char * url, ftpSysCall_t ftpSysCall,
3387         struct stat * st, char * rlbuf, size_t rlbufsiz)
3388 {
3389     FD_t fd;
3390     const char * path;
3391     int bufLength, moretodo;
3392     const char *n, *ne, *o, *oe;
3393     char * s;
3394     char * se;
3395     const char * urldn;
3396     char * bn = NULL;
3397     int nbn = 0;
3398     urlinfo u;
3399     int rc;
3400
3401     n = ne = o = oe = NULL;
3402     (void) urlPath(url, &path);
3403     if (*path == '\0')
3404         return -2;
3405
3406     switch (ftpSysCall) {
3407     case DO_FTP_GLOB:
3408         fd = ftpOpen(url, 0, 0, &u);
3409         if (fd == NULL || u == NULL)
3410             return -1;
3411
3412         u->openError = ftpReq(fd, "NLST", path);
3413         break;
3414     default:
3415         urldn = alloca_strdup(url);
3416         if ((bn = strrchr(urldn, '/')) == NULL)
3417             return -2;
3418         else if (bn == path)
3419             bn = ".";
3420         else
3421             *bn++ = '\0';
3422         nbn = strlen(bn);
3423
3424         rc = ftpChdir(urldn);           /* XXX don't care about CWD */
3425         if (rc < 0)
3426             return rc;
3427
3428         fd = ftpOpen(url, 0, 0, &u);
3429         if (fd == NULL || u == NULL)
3430             return -1;
3431
3432         /* XXX possibly should do "NLST -lais" to get st_ino/st_blocks also */
3433         u->openError = ftpReq(fd, "NLST", "-la");
3434         break;
3435     }
3436
3437     if (u->openError < 0) {
3438         fd = fdLink(fd, "error data (ftpStat)");
3439         rc = -2;
3440         goto exit;
3441     }
3442
3443     if (ftpBufAlloced == 0 || ftpBuf == NULL) {
3444         ftpBufAlloced = url_iobuf_size;
3445         ftpBuf = xcalloc(ftpBufAlloced, sizeof(ftpBuf[0]));
3446     }
3447     *ftpBuf = '\0';
3448
3449     bufLength = 0;
3450     moretodo = 1;
3451
3452     do {
3453
3454         /* XXX FIXME: realloc ftpBuf is < ~128 chars remain */
3455         if ((ftpBufAlloced - bufLength) < (1024+80)) {
3456             ftpBufAlloced <<= 2;
3457             ftpBuf = xrealloc(ftpBuf, ftpBufAlloced);
3458         }
3459         s = se = ftpBuf + bufLength;
3460         *se = '\0';
3461
3462         rc = fdFgets(fd, se, (ftpBufAlloced - bufLength));
3463         if (rc <= 0) {
3464             moretodo = 0;
3465             break;
3466         }
3467         if (ftpSysCall == DO_FTP_GLOB) {        /* XXX HACK */
3468             bufLength += strlen(se);
3469             continue;
3470         }
3471
3472         for (s = se; *s != '\0'; s = se) {
3473             int bingo;
3474
3475             while (*se && *se != '\n') se++;
3476             if (se > s && se[-1] == '\r') se[-1] = '\0';
3477             if (*se == '\0') break;
3478             *se++ = '\0';
3479
3480             if (!strncmp(s, "total ", sizeof("total ")-1)) continue;
3481
3482             o = NULL;
3483             for (bingo = 0, n = se; n >= s; n--) {
3484                 switch (*n) {
3485                 case '\0':
3486                     oe = ne = n;
3487                     break;
3488                 case ' ':
3489                     if (o || !(n[-3] == ' ' && n[-2] == '-' && n[-1] == '>')) {
3490                         while (*(++n) == ' ');
3491                         bingo++;
3492                         break;
3493                     }
3494                     for (o = n + 1; *o == ' '; o++);
3495                     n -= 3;
3496                     ne = n;
3497                     break;
3498                 default:
3499                     break;
3500                 }
3501                 if (bingo) break;
3502             }
3503
3504             if (nbn != (ne - n))        continue;       /* Same name length? */
3505             if (strncmp(n, bn, nbn))    continue;       /* Same name? */
3506
3507             moretodo = 0;
3508             break;
3509         }
3510
3511         if (moretodo && se > s) {
3512             bufLength = se - s - 1;
3513             if (s != ftpBuf)
3514                 memmove(ftpBuf, s, bufLength);
3515         } else {
3516             bufLength = 0;
3517         }
3518     } while (moretodo);
3519
3520     switch (ftpSysCall) {
3521     case DO_FTP_STAT:
3522         if (o && oe) {
3523             /* XXX FIXME: symlink, replace urldn/bn from [o,oe) and restart */
3524         }
3525         /*@fallthrough@*/
3526     case DO_FTP_LSTAT:
3527         if (st == NULL || !(n && ne)) {
3528             rc = -1;
3529         } else {
3530             rc = ((vfs_parse_ls_lga(s, st, NULL, NULL) > 0) ? 0 : -1);
3531         }
3532         break;
3533     case DO_FTP_READLINK:
3534         if (rlbuf == NULL || !(o && oe)) {
3535             rc = -1;
3536         } else {
3537             rc = oe - o;
3538             if (rc > rlbufsiz)
3539                 rc = rlbufsiz;
3540             memcpy(rlbuf, o, rc);
3541             if (rc < rlbufsiz)
3542                 rlbuf[rc] = '\0';
3543         }
3544         break;
3545     case DO_FTP_ACCESS:
3546         rc = 0;         /* XXX WRONG WRONG WRONG */
3547         break;
3548     case DO_FTP_GLOB:
3549         rc = 0;         /* XXX WRONG WRONG WRONG */
3550         break;
3551     }
3552
3553 exit:
3554     ufdClose(fd);
3555     return rc;
3556 }
3557
3558 static int ftpStat(const char * path, struct stat *st)
3559 {
3560     return ftpNLST(path, DO_FTP_STAT, st, NULL, 0);
3561 }
3562
3563 static int ftpLstat(const char * path, struct stat *st) {
3564     int rc;
3565     rc = ftpNLST(path, DO_FTP_LSTAT, st, NULL, 0);
3566 if (_rpmio_debug)
3567 fprintf(stderr, "*** ftpLstat(%s) rc %d\n", path, rc);
3568     return rc;
3569 }
3570
3571 static int ftpReadlink(const char * path, char * buf, size_t bufsiz) {
3572     return ftpNLST(path, DO_FTP_READLINK, NULL, buf, bufsiz);
3573 }
3574
3575 static int ftpGlob(const char * path, int flags,
3576                 int errfunc(const char * epath, int eerno), glob_t * pglob)
3577 {
3578     int rc;
3579
3580     if (pglob == NULL)
3581         return -2;
3582     rc = ftpNLST(path, DO_FTP_GLOB, NULL, NULL, 0);
3583 if (_rpmio_debug)
3584 fprintf(stderr, "*** ftpGlob(%s,0x%x,%p,%p) ftpNLST rc %d\n", path, flags, errfunc, pglob, rc);
3585     if (rc)
3586         return rc;
3587     rc = poptParseArgvString(ftpBuf, &pglob->gl_pathc, (const char ***)&pglob->gl_pathv);
3588     pglob->gl_offs = -1;        /* XXX HACK HACK HACK */
3589     return rc;
3590 }
3591
3592 static void ftpGlobfree(glob_t * pglob) {
3593 if (_rpmio_debug)
3594 fprintf(stderr, "*** ftpGlobfree(%p)\n", pglob);
3595     if (pglob->gl_offs == -1)   /* XXX HACK HACK HACK */
3596         xfree(pglob->gl_pathv);
3597 }
3598
3599 int Stat(const char * path, struct stat * st) {
3600     const char * lpath;
3601     int ut = urlPath(path, &lpath);
3602
3603 if (_rpmio_debug)
3604 fprintf(stderr, "*** Stat(%s,%p)\n", path, st);
3605     switch (ut) {
3606     case URL_IS_FTP:
3607         return ftpStat(path, st);
3608         /*@notreached@*/ break;
3609     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3610     case URL_IS_PATH:
3611         path = lpath;
3612         /*@fallthrough@*/
3613     case URL_IS_UNKNOWN:
3614         break;
3615     case URL_IS_DASH:
3616     default:
3617         return -2;
3618         /*@notreached@*/ break;
3619     }
3620     return stat(path, st);
3621 }
3622
3623 int Lstat(const char * path, struct stat * st) {
3624     const char * lpath;
3625     int ut = urlPath(path, &lpath);
3626
3627 if (_rpmio_debug)
3628 fprintf(stderr, "*** Lstat(%s,%p)\n", path, st);
3629     switch (ut) {
3630     case URL_IS_FTP:
3631         return ftpLstat(path, st);
3632         /*@notreached@*/ break;
3633     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3634     case URL_IS_PATH:
3635         path = lpath;
3636         /*@fallthrough@*/
3637     case URL_IS_UNKNOWN:
3638         break;
3639     case URL_IS_DASH:
3640     default:
3641         return -2;
3642         /*@notreached@*/ break;
3643     }
3644     return lstat(path, st);
3645 }
3646
3647 int Readlink(const char * path, char * buf, size_t bufsiz) {
3648     const char * lpath;
3649     int ut = urlPath(path, &lpath);
3650
3651     switch (ut) {
3652     case URL_IS_FTP:
3653         return ftpReadlink(path, buf, bufsiz);
3654         /*@notreached@*/ break;
3655     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3656     case URL_IS_PATH:
3657         path = lpath;
3658         /*@fallthrough@*/
3659     case URL_IS_UNKNOWN:
3660         break;
3661     case URL_IS_DASH:
3662     default:
3663         return -2;
3664         /*@notreached@*/ break;
3665     }
3666     return readlink(path, buf, bufsiz);
3667 }
3668
3669 int Access(const char * path, int amode) {
3670     const char * lpath;
3671     int ut = urlPath(path, &lpath);
3672
3673 if (_rpmio_debug)
3674 fprintf(stderr, "*** Access(%s,%d)\n", path, amode);
3675     switch (ut) {
3676     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
3677     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3678     case URL_IS_PATH:
3679         path = lpath;
3680         /*@fallthrough@*/
3681     case URL_IS_UNKNOWN:
3682         break;
3683     case URL_IS_DASH:
3684     default:
3685         return -2;
3686         /*@notreached@*/ break;
3687     }
3688     return access(path, amode);
3689 }
3690
3691 int Glob(const char *path, int flags,
3692         int errfunc(const char * epath, int eerrno), glob_t *pglob)
3693 {
3694     const char * lpath;
3695     int ut = urlPath(path, &lpath);
3696
3697 if (_rpmio_debug)
3698 fprintf(stderr, "*** Glob(%s,0x%x,%p,%p)\n", path, flags, errfunc, pglob);
3699     switch (ut) {
3700     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
3701         return ftpGlob(path, flags, errfunc, pglob);
3702         /*@notreached@*/ break;
3703     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3704     case URL_IS_PATH:
3705         path = lpath;
3706         /*@fallthrough@*/
3707     case URL_IS_UNKNOWN:
3708         break;
3709     case URL_IS_DASH:
3710     default:
3711         return -2;
3712         /*@notreached@*/ break;
3713     }
3714     return glob(path, flags, errfunc, pglob);
3715 }
3716
3717 void Globfree(glob_t *pglob)
3718 {
3719 if (_rpmio_debug)
3720 fprintf(stderr, "*** Globfree(%p)\n", pglob);
3721     if (pglob->gl_offs == -1) /* XXX HACK HACK HACK */
3722         ftpGlobfree(pglob);
3723     else
3724         globfree(pglob);
3725 }
3726
3727 DIR * Opendir(const char * path)
3728 {
3729     const char * lpath;
3730     int ut = urlPath(path, &lpath);
3731
3732 if (_rpmio_debug)
3733 fprintf(stderr, "*** Opendir(%s)\n", path);
3734     switch (ut) {
3735     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
3736     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
3737     case URL_IS_PATH:
3738         path = lpath;
3739         /*@fallthrough@*/
3740     case URL_IS_UNKNOWN:
3741         break;
3742     case URL_IS_DASH:
3743     default:
3744         return NULL;
3745         /*@notreached@*/ break;
3746     }
3747     return opendir(path);
3748 }
3749
3750 struct dirent * Readdir(DIR * dir)
3751 {
3752 if (_rpmio_debug)
3753 fprintf(stderr, "*** Readdir(%p)\n", dir);
3754     return readdir(dir);
3755 }
3756
3757 int Closedir(DIR * dir)
3758 {
3759 if (_rpmio_debug)
3760 fprintf(stderr, "*** Closedir(%p)\n", dir);
3761     return closedir(dir);
3762 }
3763
3764 static struct FDIO_s fpio_s = {
3765   ufdRead, ufdWrite, fdSeek, ufdClose, XfdLink, XfdFree, XfdNew, fdFileno,
3766   ufdOpen, NULL, fdGetFp, NULL, Mkdir, Chdir, Rmdir, Rename, Unlink
3767 };
3768 FDIO_t fpio = /*@-compmempass@*/ &fpio_s /*@=compmempass@*/ ;