Doxygen annotations.
[platform/upstream/rpm.git] / rpmio / rpmrpc.c
1 /** \ingroup rpmio
2  * \file rpmio/rpmrpc.c
3  */
4
5 #include "system.h"
6
7 #include <rpmio_internal.h>
8 #include <popt.h>
9 #include "ugid.h"
10
11 extern int _rpmio_debug;
12
13 /* =============================================================== */
14 static int ftpMkdir(const char * path, /*@unused@*/ mode_t mode) {
15     int rc;
16     if ((rc = ftpCmd("MKD", path, NULL)) != 0)
17         return rc;
18 #if NOTYET
19     {   char buf[20];
20         sprintf(buf, " 0%o", mode);
21         (void) ftpCmd("SITE CHMOD", path, buf);
22     }
23 #endif
24     return rc;
25 }
26
27 static int ftpChdir(const char * path) {
28     return ftpCmd("CWD", path, NULL);
29 }
30
31 static int ftpRmdir(const char * path) {
32     return ftpCmd("RMD", path, NULL);
33 }
34
35 static int ftpRename(const char * oldpath, const char * newpath) {
36     int rc;
37     if ((rc = ftpCmd("RNFR", oldpath, NULL)) != 0)
38         return rc;
39     return ftpCmd("RNTO", newpath, NULL);
40 }
41
42 static int ftpUnlink(const char * path) {
43     return ftpCmd("DELE", path, NULL);
44 }
45
46 /* =============================================================== */
47 /* XXX rebuilddb.c: analogues to mkdir(2)/rmdir(2). */
48 int Mkdir (const char *path, mode_t mode) {
49     const char * lpath;
50     int ut = urlPath(path, &lpath);
51
52     switch (ut) {
53     case URL_IS_FTP:
54         return ftpMkdir(path, mode);
55         /*@notreached@*/ break;
56     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
57     case URL_IS_PATH:
58         path = lpath;
59         /*@fallthrough@*/
60     case URL_IS_UNKNOWN:
61         break;
62     case URL_IS_DASH:
63     default:
64         return -2;
65         /*@notreached@*/ break;
66     }
67     return mkdir(path, mode);
68 }
69
70 int Chdir (const char *path) {
71     const char * lpath;
72     int ut = urlPath(path, &lpath);
73
74     switch (ut) {
75     case URL_IS_FTP:
76         return ftpChdir(path);
77         /*@notreached@*/ break;
78     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
79     case URL_IS_PATH:
80         path = lpath;
81         /*@fallthrough@*/
82     case URL_IS_UNKNOWN:
83         break;
84     case URL_IS_DASH:
85     default:
86         return -2;
87         /*@notreached@*/ break;
88     }
89     return chdir(path);
90 }
91
92 int Rmdir (const char *path) {
93     const char * lpath;
94     int ut = urlPath(path, &lpath);
95
96     switch (ut) {
97     case URL_IS_FTP:
98         return ftpRmdir(path);
99         /*@notreached@*/ break;
100     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
101     case URL_IS_PATH:
102         path = lpath;
103         /*@fallthrough@*/
104     case URL_IS_UNKNOWN:
105         break;
106     case URL_IS_DASH:
107     default:
108         return -2;
109         /*@notreached@*/ break;
110     }
111     return rmdir(path);
112 }
113
114 /* XXX rpmdb.c: analogue to rename(2). */
115
116 int Rename (const char *oldpath, const char * newpath) {
117     const char *oe = NULL;
118     const char *ne = NULL;
119     int oldut, newut;
120
121     /* XXX lib/install.c used to rely on this behavior. */
122     if (!strcmp(oldpath, newpath)) return 0;
123
124     oldut = urlPath(oldpath, &oe);
125     switch (oldut) {
126     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
127     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
128     case URL_IS_PATH:
129     case URL_IS_UNKNOWN:
130         break;
131     case URL_IS_DASH:
132     default:
133         return -2;
134         /*@notreached@*/ break;
135     }
136
137     newut = urlPath(newpath, &ne);
138     switch (newut) {
139     case URL_IS_FTP:
140 if (_rpmio_debug)
141 fprintf(stderr, "*** rename old %*s new %*s\n", (int)(oe - oldpath), oldpath, (int)(ne - newpath), newpath);
142         if (!(oldut == newut && oe && ne && (oe - oldpath) == (ne - newpath) &&
143             !strncasecmp(oldpath, newpath, (oe - oldpath))))
144             return -2;
145         return ftpRename(oldpath, newpath);
146         /*@notreached@*/ break;
147     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
148     case URL_IS_PATH:
149         oldpath = oe;
150         newpath = ne;
151         break;
152     case URL_IS_UNKNOWN:
153         break;
154     case URL_IS_DASH:
155     default:
156         return -2;
157         /*@notreached@*/ break;
158     }
159     return rename(oldpath, newpath);
160 }
161
162 int Link (const char *oldpath, const char * newpath) {
163     const char *oe = NULL;
164     const char *ne = NULL;
165     int oldut, newut;
166
167     oldut = urlPath(oldpath, &oe);
168     switch (oldut) {
169     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
170     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
171     case URL_IS_PATH:
172     case URL_IS_UNKNOWN:
173         break;
174     case URL_IS_DASH:
175     default:
176         return -2;
177         /*@notreached@*/ break;
178     }
179
180     newut = urlPath(newpath, &ne);
181     switch (newut) {
182     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
183     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
184     case URL_IS_PATH:
185 if (_rpmio_debug)
186 fprintf(stderr, "*** link old %*s new %*s\n", (int)(oe - oldpath), oldpath, (int)(ne - newpath), newpath);
187         if (!(oldut == newut && oe && ne && (oe - oldpath) == (ne - newpath) &&
188             !strncasecmp(oldpath, newpath, (oe - oldpath))))
189             return -2;
190         oldpath = oe;
191         newpath = ne;
192         break;
193     case URL_IS_UNKNOWN:
194         break;
195     case URL_IS_DASH:
196     default:
197         return -2;
198         /*@notreached@*/ break;
199     }
200     return link(oldpath, newpath);
201 }
202
203 /* XXX build/build.c: analogue to unlink(2). */
204
205 int Unlink(const char * path) {
206     const char * lpath;
207     int ut = urlPath(path, &lpath);
208
209     switch (ut) {
210     case URL_IS_FTP:
211         return ftpUnlink(path);
212         /*@notreached@*/ break;
213     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
214     case URL_IS_PATH:
215         path = lpath;
216         /*@fallthrough@*/
217     case URL_IS_UNKNOWN:
218         break;
219     case URL_IS_DASH:
220     default:
221         return -2;
222         /*@notreached@*/ break;
223     }
224     return unlink(path);
225 }
226
227 /* XXX swiped from mc-4.5.39-pre9 vfs/ftpfs.c */
228
229 #define g_strdup        xstrdup
230 #define g_free          xfree
231
232 /*
233  * FIXME: this is broken. It depends on mc not crossing border on month!
234  */
235 static int current_mday;
236 static int current_mon;
237 static int current_year;
238
239 /* Following stuff (parse_ls_lga) is used by ftpfs and extfs */
240 #define MAXCOLS         30
241
242 static char *columns [MAXCOLS]; /* Points to the string in column n */
243 static int   column_ptr [MAXCOLS]; /* Index from 0 to the starting positions of the columns */
244
245 static int
246 vfs_split_text (char *p)
247 {
248     char *original = p;
249     int  numcols;
250
251
252     for (numcols = 0; *p && numcols < MAXCOLS; numcols++){
253         while (*p == ' ' || *p == '\r' || *p == '\n'){
254             *p = 0;
255             p++;
256         }
257         columns [numcols] = p;
258         column_ptr [numcols] = p - original;
259         while (*p && *p != ' ' && *p != '\r' && *p != '\n')
260             p++;
261     }
262     return numcols;
263 }
264
265 static int
266 is_num (int idx)
267 {
268     if (!columns [idx] || columns [idx][0] < '0' || columns [idx][0] > '9')
269         return 0;
270     return 1;
271 }
272
273 static int
274 is_dos_date(char *str)
275 {
276     if (strlen(str) == 8 && str[2] == str[5] && strchr("\\-/", (int)str[2]) != NULL)
277         return (1);
278
279     return (0);
280 }
281
282 static int
283 is_week (char *str, struct tm *tim)
284 {
285     static char *week = "SunMonTueWedThuFriSat";
286     char *pos;
287
288     if((pos=strstr(week, str)) != NULL){
289         if(tim != NULL)
290             tim->tm_wday = (pos - week)/3;
291         return (1);
292     }
293     return (0);    
294 }
295
296 static int
297 is_month (char *str, struct tm *tim)
298 {
299     static char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
300     char *pos;
301     
302     if((pos=strstr(month, str)) != NULL){
303         if(tim != NULL)
304             tim->tm_mon = (pos - month)/3;
305         return (1);
306     }
307     return (0);
308 }
309
310 static int
311 is_time (char *str, struct tm *tim)
312 {
313     char *p, *p2;
314
315     if ((p=strchr(str, ':')) && (p2=strrchr(str, ':'))) {
316         if (p != p2) {
317             if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
318                 return (0);
319         }
320         else {
321             if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
322                 return (0);
323         }
324     }
325     else 
326         return (0);
327     
328     return (1);
329 }
330
331 static int is_year(char *str, struct tm *tim)
332 {
333     long year;
334
335     if (strchr(str,':'))
336         return (0);
337
338     if (strlen(str)!=4)
339         return (0);
340
341     if (sscanf(str, "%ld", &year) != 1)
342         return (0);
343
344     if (year < 1900 || year > 3000)
345         return (0);
346
347     tim->tm_year = (int) (year - 1900);
348
349     return (1);
350 }
351
352 /*
353  * FIXME: this is broken. Consider following entry:
354  * -rwx------   1 root     root            1 Aug 31 10:04 2904 1234
355  * where "2904 1234" is filename. Well, this code decodes it as year :-(.
356  */
357
358 static int
359 vfs_parse_filetype (char c)
360 {
361     switch (c){
362         case 'd': return S_IFDIR; 
363         case 'b': return S_IFBLK;
364         case 'c': return S_IFCHR;
365         case 'l': return S_IFLNK;
366         case 's':
367 #ifdef IS_IFSOCK /* And if not, we fall through to IFIFO, which is pretty close */
368                   return S_IFSOCK;
369 #endif
370         case 'p': return S_IFIFO;
371         case 'm': case 'n':             /* Don't know what these are :-) */
372         case '-': case '?': return S_IFREG;
373         default: return -1;
374     }
375 }
376
377 static int vfs_parse_filemode (char *p)
378 {       /* converts rw-rw-rw- into 0666 */
379     int res = 0;
380     switch (*(p++)){
381         case 'r': res |= 0400; break;
382         case '-': break;
383         default: return -1;
384     }
385     switch (*(p++)){
386         case 'w': res |= 0200; break;
387         case '-': break;
388         default: return -1;
389     }
390     switch (*(p++)){
391         case 'x': res |= 0100; break;
392         case 's': res |= 0100 | S_ISUID; break;
393         case 'S': res |= S_ISUID; break;
394         case '-': break;
395         default: return -1;
396     }
397     switch (*(p++)){
398         case 'r': res |= 0040; break;
399         case '-': break;
400         default: return -1;
401     }
402     switch (*(p++)){
403         case 'w': res |= 0020; break;
404         case '-': break;
405         default: return -1;
406     }
407     switch (*(p++)){
408         case 'x': res |= 0010; break;
409         case 's': res |= 0010 | S_ISGID; break;
410         case 'l': /* Solaris produces these */
411         case 'S': res |= S_ISGID; break;
412         case '-': break;
413         default: return -1;
414     }
415     switch (*(p++)){
416         case 'r': res |= 0004; break;
417         case '-': break;
418         default: return -1;
419     }
420     switch (*(p++)){
421         case 'w': res |= 0002; break;
422         case '-': break;
423         default: return -1;
424     }
425     switch (*(p++)){
426         case 'x': res |= 0001; break;
427         case 't': res |= 0001 | S_ISVTX; break;
428         case 'T': res |= S_ISVTX; break;
429         case '-': break;
430         default: return -1;
431     }
432     return res;
433 }
434
435 static int vfs_parse_filedate(int idx, time_t *t)
436 {       /* This thing parses from idx in columns[] array */
437
438     char *p;
439     struct tm tim;
440     int d[3];
441     int got_year = 0;
442
443     /* Let's setup default time values */
444     tim.tm_year = current_year;
445     tim.tm_mon  = current_mon;
446     tim.tm_mday = current_mday;
447     tim.tm_hour = 0;
448     tim.tm_min  = 0;
449     tim.tm_sec  = 0;
450     tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
451     
452     p = columns [idx++];
453     
454     /* We eat weekday name in case of extfs */
455     if(is_week(p, &tim))
456         p = columns [idx++];
457
458     /* Month name */
459     if(is_month(p, &tim)){
460         /* And we expect, it followed by day number */
461         if (is_num (idx))
462             tim.tm_mday = (int)atol (columns [idx++]);
463         else
464             return 0; /* No day */
465
466     } else {
467         /* We usually expect:
468            Mon DD hh:mm
469            Mon DD  YYYY
470            But in case of extfs we allow these date formats:
471            Mon DD YYYY hh:mm
472            Mon DD hh:mm YYYY
473            Wek Mon DD hh:mm:ss YYYY
474            MM-DD-YY hh:mm
475            where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
476            YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
477
478         /* Here just this special case with MM-DD-YY */
479         if (is_dos_date(p)){
480             p[2] = p[5] = '-';
481             
482             if(sscanf(p, "%2d-%2d-%2d", &d[0], &d[1], &d[2]) == 3){
483             /*  We expect to get:
484                 1. MM-DD-YY
485                 2. DD-MM-YY
486                 3. YY-MM-DD
487                 4. YY-DD-MM  */
488                 
489                 /* Hmm... maybe, next time :)*/
490                 
491                 /* At last, MM-DD-YY */
492                 d[0]--; /* Months are zerobased */
493                 /* Y2K madness */
494                 if(d[2] < 70)
495                     d[2] += 100;
496
497                 tim.tm_mon  = d[0];
498                 tim.tm_mday = d[1];
499                 tim.tm_year = d[2];
500                 got_year = 1;
501             } else
502                 return 0; /* sscanf failed */
503         } else
504             return 0; /* unsupported format */
505     }
506
507     /* Here we expect to find time and/or year */
508     
509     if (is_num (idx)) {
510         if(is_time(columns[idx], &tim) || (got_year = is_year(columns[idx], &tim))) {
511         idx++;
512
513         /* This is a special case for ctime() or Mon DD YYYY hh:mm */
514         if(is_num (idx) && 
515             ((got_year = is_year(columns[idx], &tim)) || is_time(columns[idx], &tim)))
516                 idx++; /* time & year or reverse */
517         } /* only time or date */
518     }
519     else 
520         return 0; /* Nor time or date */
521
522     /*
523      * If the date is less than 6 months in the past, it is shown without year
524      * other dates in the past or future are shown with year but without time
525      * This does not check for years before 1900 ... I don't know, how
526      * to represent them at all
527      */
528     if (!got_year &&
529         current_mon < 6 && current_mon < tim.tm_mon && 
530         tim.tm_mon - current_mon >= 6)
531
532         tim.tm_year--;
533
534     if ((*t = mktime(&tim)) < 0)
535         *t = 0;
536     return idx;
537 }
538
539 static int
540 vfs_parse_ls_lga (char *p, struct stat *s, char **filename, char **linkname)
541 {
542     int idx, idx2, num_cols;
543     int i;
544     char *p_copy;
545     
546     if (strncmp (p, "total", 5) == 0)
547         return 0;
548
549     p_copy = g_strdup(p);
550 /* XXX FIXME: parse out inode number from "NLST -lai ." */
551 /* XXX FIXME: parse out sizein blocks from "NLST -lais ." */
552
553     if ((i = vfs_parse_filetype(*(p++))) == -1)
554         goto error;
555
556     s->st_mode = i;
557     if (*p == ' ')      /* Notwell 4 */
558         p++;
559     if (*p == '['){
560         if (strlen (p) <= 8 || p [8] != ']')
561             goto error;
562         /* Should parse here the Notwell permissions :) */
563         if (S_ISDIR (s->st_mode))
564             s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
565         else
566             s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
567         p += 9;
568     } else {
569         if ((i = vfs_parse_filemode(p)) == -1)
570             goto error;
571         s->st_mode |= i;
572         p += 9;
573
574         /* This is for an extra ACL attribute (HP-UX) */
575         if (*p == '+')
576             p++;
577     }
578
579     g_free(p_copy);
580     p_copy = g_strdup(p);
581     num_cols = vfs_split_text (p);
582
583     s->st_nlink = atol (columns [0]);
584     if (s->st_nlink < 0)
585         goto error;
586
587     if (!is_num (1))
588 #ifdef  HACK
589         s->st_uid = finduid (columns [1]);
590 #else
591         unameToUid (columns [1], &s->st_uid);
592 #endif
593     else
594         s->st_uid = (uid_t) atol (columns [1]);
595
596     /* Mhm, the ls -lg did not produce a group field */
597     for (idx = 3; idx <= 5; idx++) 
598         if (is_month(columns [idx], NULL) || is_week(columns [idx], NULL) || is_dos_date(columns[idx]))
599             break;
600
601     if (idx == 6 || (idx == 5 && !S_ISCHR (s->st_mode) && !S_ISBLK (s->st_mode)))
602         goto error;
603
604     /* We don't have gid */     
605     if (idx == 3 || (idx == 4 && (S_ISCHR(s->st_mode) || S_ISBLK (s->st_mode))))
606         idx2 = 2;
607     else { 
608         /* We have gid field */
609         if (is_num (2))
610             s->st_gid = (gid_t) atol (columns [2]);
611         else
612 #ifdef  HACK
613             s->st_gid = findgid (columns [2]);
614 #else
615             gnameToGid (columns [1], &s->st_gid);
616 #endif
617         idx2 = 3;
618     }
619
620     /* This is device */
621     if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode)){
622         int maj, min;
623         
624         if (!is_num (idx2) || sscanf(columns [idx2], " %d,", &maj) != 1)
625             goto error;
626         
627         if (!is_num (++idx2) || sscanf(columns [idx2], " %d", &min) != 1)
628             goto error;
629         
630 #ifdef HAVE_ST_RDEV
631         s->st_rdev = ((maj & 0xff) << 8) | (min & 0xffff00ff);
632 #endif
633         s->st_size = 0;
634         
635     } else {
636         /* Common file size */
637         if (!is_num (idx2))
638             goto error;
639         
640         s->st_size = (size_t) atol (columns [idx2]);
641 #ifdef HAVE_ST_RDEV
642         s->st_rdev = 0;
643 #endif
644     }
645
646     idx = vfs_parse_filedate(idx, &s->st_mtime);
647     if (!idx)
648         goto error;
649     /* Use resulting time value */
650     s->st_atime = s->st_ctime = s->st_mtime;
651     s->st_dev = 0;
652     s->st_ino = 0;
653 #ifdef HAVE_ST_BLKSIZE
654     s->st_blksize = 512;
655 #endif
656 #ifdef HAVE_ST_BLOCKS
657     s->st_blocks = (s->st_size + 511) / 512;
658 #endif
659
660     for (i = idx + 1, idx2 = 0; i < num_cols; i++ ) 
661         if (strcmp (columns [i], "->") == 0){
662             idx2 = i;
663             break;
664         }
665     
666     if (((S_ISLNK (s->st_mode) || 
667         (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
668         && idx2){
669         int p;
670         char *s;
671             
672         if (filename){
673 #ifdef HACK
674             s = g_strndup (p_copy + column_ptr [idx], column_ptr [idx2] - column_ptr [idx] - 1);
675 #else
676             int nb = column_ptr [idx2] - column_ptr [idx] - 1;
677             s = xmalloc(nb+1);
678             strncpy(s, p_copy + column_ptr [idx], nb);
679 #endif
680             *filename = s;
681         }
682         if (linkname){
683             s = g_strdup (p_copy + column_ptr [idx2+1]);
684             p = strlen (s);
685             if (s [p-1] == '\r' || s [p-1] == '\n')
686                 s [p-1] = 0;
687             if (s [p-2] == '\r' || s [p-2] == '\n')
688                 s [p-2] = 0;
689                 
690             *linkname = s;
691         }
692     } else {
693         /* Extract the filename from the string copy, not from the columns
694          * this way we have a chance of entering hidden directories like ". ."
695          */
696         if (filename){
697             /* 
698             *filename = g_strdup (columns [idx++]);
699             */
700             int p;
701             char *s;
702             
703             s = g_strdup (p_copy + column_ptr [idx++]);
704             p = strlen (s);
705             /* g_strchomp(); */
706             if (s [p-1] == '\r' || s [p-1] == '\n')
707                 s [p-1] = 0;
708             if (s [p-2] == '\r' || s [p-2] == '\n')
709                 s [p-2] = 0;
710             
711             *filename = s;
712         }
713         if (linkname)
714             *linkname = NULL;
715     }
716     g_free (p_copy);
717     return 1;
718
719 error:
720 #ifdef  HACK
721     {
722       static int errorcount = 0;
723
724       if (++errorcount < 5) {
725         message_1s (1, "Could not parse:", p_copy);
726       } else if (errorcount == 5)
727         message_1s (1, "More parsing errors will be ignored.", "(sorry)" );
728     }
729 #endif
730
731     if (p_copy != p)            /* Carefull! */
732         g_free (p_copy);
733     return 0;
734 }
735
736 typedef enum {
737         DO_FTP_STAT     = 1,
738         DO_FTP_LSTAT    = 2,
739         DO_FTP_READLINK = 3,
740         DO_FTP_ACCESS   = 4,
741         DO_FTP_GLOB     = 5
742 } ftpSysCall_t;
743 static size_t ftpBufAlloced = 0;
744 static char * ftpBuf = NULL;
745         
746 #define alloca_strdup(_s)       strcpy(alloca(strlen(_s)+1), (_s))
747
748 static int ftpNLST(const char * url, ftpSysCall_t ftpSysCall,
749         struct stat * st, char * rlbuf, size_t rlbufsiz)
750 {
751     FD_t fd;
752     const char * path;
753     int bufLength, moretodo;
754     const char *n, *ne, *o, *oe;
755     char * s;
756     char * se;
757     const char * urldn;
758     char * bn = NULL;
759     int nbn = 0;
760     urlinfo u;
761     int rc;
762
763     n = ne = o = oe = NULL;
764     (void) urlPath(url, &path);
765     if (*path == '\0')
766         return -2;
767
768     switch (ftpSysCall) {
769     case DO_FTP_GLOB:
770         fd = ftpOpen(url, 0, 0, &u);
771         if (fd == NULL || u == NULL)
772             return -1;
773
774         u->openError = ftpReq(fd, "NLST", path);
775         break;
776     default:
777         urldn = alloca_strdup(url);
778         if ((bn = strrchr(urldn, '/')) == NULL)
779             return -2;
780         else if (bn == path)
781             bn = ".";
782         else
783             *bn++ = '\0';
784         nbn = strlen(bn);
785
786         rc = ftpChdir(urldn);           /* XXX don't care about CWD */
787         if (rc < 0)
788             return rc;
789
790         fd = ftpOpen(url, 0, 0, &u);
791         if (fd == NULL || u == NULL)
792             return -1;
793
794         /* XXX possibly should do "NLST -lais" to get st_ino/st_blocks also */
795         u->openError = ftpReq(fd, "NLST", "-la");
796         break;
797     }
798
799     if (u->openError < 0) {
800         fd = fdLink(fd, "error data (ftpStat)");
801         rc = -2;
802         goto exit;
803     }
804
805     if (ftpBufAlloced == 0 || ftpBuf == NULL) {
806         ftpBufAlloced = url_iobuf_size;
807         ftpBuf = xcalloc(ftpBufAlloced, sizeof(ftpBuf[0]));
808     }
809     *ftpBuf = '\0';
810
811     bufLength = 0;
812     moretodo = 1;
813
814     do {
815
816         /* XXX FIXME: realloc ftpBuf is < ~128 chars remain */
817         if ((ftpBufAlloced - bufLength) < (1024+80)) {
818             ftpBufAlloced <<= 2;
819             ftpBuf = xrealloc(ftpBuf, ftpBufAlloced);
820         }
821         s = se = ftpBuf + bufLength;
822         *se = '\0';
823
824         rc = fdFgets(fd, se, (ftpBufAlloced - bufLength));
825         if (rc <= 0) {
826             moretodo = 0;
827             break;
828         }
829         if (ftpSysCall == DO_FTP_GLOB) {        /* XXX HACK */
830             bufLength += strlen(se);
831             continue;
832         }
833
834         for (s = se; *s != '\0'; s = se) {
835             int bingo;
836
837             while (*se && *se != '\n') se++;
838             if (se > s && se[-1] == '\r') se[-1] = '\0';
839             if (*se == '\0') break;
840             *se++ = '\0';
841
842             if (!strncmp(s, "total ", sizeof("total ")-1)) continue;
843
844             o = NULL;
845             for (bingo = 0, n = se; n >= s; n--) {
846                 switch (*n) {
847                 case '\0':
848                     oe = ne = n;
849                     break;
850                 case ' ':
851                     if (o || !(n[-3] == ' ' && n[-2] == '-' && n[-1] == '>')) {
852                         while (*(++n) == ' ');
853                         bingo++;
854                         break;
855                     }
856                     for (o = n + 1; *o == ' '; o++);
857                     n -= 3;
858                     ne = n;
859                     break;
860                 default:
861                     break;
862                 }
863                 if (bingo) break;
864             }
865
866             if (nbn != (ne - n))        continue;       /* Same name length? */
867             if (strncmp(n, bn, nbn))    continue;       /* Same name? */
868
869             moretodo = 0;
870             break;
871         }
872
873         if (moretodo && se > s) {
874             bufLength = se - s - 1;
875             if (s != ftpBuf)
876                 memmove(ftpBuf, s, bufLength);
877         } else {
878             bufLength = 0;
879         }
880     } while (moretodo);
881
882     switch (ftpSysCall) {
883     case DO_FTP_STAT:
884         if (o && oe) {
885             /* XXX FIXME: symlink, replace urldn/bn from [o,oe) and restart */
886         }
887         /*@fallthrough@*/
888     case DO_FTP_LSTAT:
889         if (st == NULL || !(n && ne)) {
890             rc = -1;
891         } else {
892             rc = ((vfs_parse_ls_lga(s, st, NULL, NULL) > 0) ? 0 : -1);
893         }
894         break;
895     case DO_FTP_READLINK:
896         if (rlbuf == NULL || !(o && oe)) {
897             rc = -1;
898         } else {
899             rc = oe - o;
900             if (rc > rlbufsiz)
901                 rc = rlbufsiz;
902             memcpy(rlbuf, o, rc);
903             if (rc < rlbufsiz)
904                 rlbuf[rc] = '\0';
905         }
906         break;
907     case DO_FTP_ACCESS:
908         rc = 0;         /* XXX WRONG WRONG WRONG */
909         break;
910     case DO_FTP_GLOB:
911         rc = 0;         /* XXX WRONG WRONG WRONG */
912         break;
913     }
914
915 exit:
916     ufdClose(fd);
917     return rc;
918 }
919
920 static int ftpStat(const char * path, struct stat *st)
921 {
922     return ftpNLST(path, DO_FTP_STAT, st, NULL, 0);
923 }
924
925 static int ftpLstat(const char * path, struct stat *st) {
926     int rc;
927     rc = ftpNLST(path, DO_FTP_LSTAT, st, NULL, 0);
928 if (_rpmio_debug)
929 fprintf(stderr, "*** ftpLstat(%s) rc %d\n", path, rc);
930     return rc;
931 }
932
933 static int ftpReadlink(const char * path, char * buf, size_t bufsiz) {
934     return ftpNLST(path, DO_FTP_READLINK, NULL, buf, bufsiz);
935 }
936
937 static int ftpGlob(const char * path, int flags,
938                 int errfunc(const char * epath, int eerno), glob_t * pglob)
939 {
940     int rc;
941
942     if (pglob == NULL)
943         return -2;
944     rc = ftpNLST(path, DO_FTP_GLOB, NULL, NULL, 0);
945 if (_rpmio_debug)
946 fprintf(stderr, "*** ftpGlob(%s,0x%x,%p,%p) ftpNLST rc %d\n", path, flags, errfunc, pglob, rc);
947     if (rc)
948         return rc;
949     rc = poptParseArgvString(ftpBuf, &pglob->gl_pathc, (const char ***)&pglob->gl_pathv);
950     pglob->gl_offs = -1;        /* XXX HACK HACK HACK */
951     return rc;
952 }
953
954 static void ftpGlobfree(glob_t * pglob) {
955 if (_rpmio_debug)
956 fprintf(stderr, "*** ftpGlobfree(%p)\n", pglob);
957     if (pglob->gl_offs == -1)   /* XXX HACK HACK HACK */
958         xfree(pglob->gl_pathv);
959 }
960
961 int Stat(const char * path, struct stat * st) {
962     const char * lpath;
963     int ut = urlPath(path, &lpath);
964
965 if (_rpmio_debug)
966 fprintf(stderr, "*** Stat(%s,%p)\n", path, st);
967     switch (ut) {
968     case URL_IS_FTP:
969         return ftpStat(path, st);
970         /*@notreached@*/ break;
971     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
972     case URL_IS_PATH:
973         path = lpath;
974         /*@fallthrough@*/
975     case URL_IS_UNKNOWN:
976         break;
977     case URL_IS_DASH:
978     default:
979         return -2;
980         /*@notreached@*/ break;
981     }
982     return stat(path, st);
983 }
984
985 int Lstat(const char * path, struct stat * st) {
986     const char * lpath;
987     int ut = urlPath(path, &lpath);
988
989 if (_rpmio_debug)
990 fprintf(stderr, "*** Lstat(%s,%p)\n", path, st);
991     switch (ut) {
992     case URL_IS_FTP:
993         return ftpLstat(path, st);
994         /*@notreached@*/ break;
995     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
996     case URL_IS_PATH:
997         path = lpath;
998         /*@fallthrough@*/
999     case URL_IS_UNKNOWN:
1000         break;
1001     case URL_IS_DASH:
1002     default:
1003         return -2;
1004         /*@notreached@*/ break;
1005     }
1006     return lstat(path, st);
1007 }
1008
1009 int Readlink(const char * path, char * buf, size_t bufsiz) {
1010     const char * lpath;
1011     int ut = urlPath(path, &lpath);
1012
1013     switch (ut) {
1014     case URL_IS_FTP:
1015         return ftpReadlink(path, buf, bufsiz);
1016         /*@notreached@*/ break;
1017     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1018     case URL_IS_PATH:
1019         path = lpath;
1020         /*@fallthrough@*/
1021     case URL_IS_UNKNOWN:
1022         break;
1023     case URL_IS_DASH:
1024     default:
1025         return -2;
1026         /*@notreached@*/ break;
1027     }
1028     return readlink(path, buf, bufsiz);
1029 }
1030
1031 int Access(const char * path, int amode) {
1032     const char * lpath;
1033     int ut = urlPath(path, &lpath);
1034
1035 if (_rpmio_debug)
1036 fprintf(stderr, "*** Access(%s,%d)\n", path, amode);
1037     switch (ut) {
1038     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1039     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1040     case URL_IS_PATH:
1041         path = lpath;
1042         /*@fallthrough@*/
1043     case URL_IS_UNKNOWN:
1044         break;
1045     case URL_IS_DASH:
1046     default:
1047         return -2;
1048         /*@notreached@*/ break;
1049     }
1050     return access(path, amode);
1051 }
1052
1053 int Glob(const char *path, int flags,
1054         int errfunc(const char * epath, int eerrno), glob_t *pglob)
1055 {
1056     const char * lpath;
1057     int ut = urlPath(path, &lpath);
1058
1059 if (_rpmio_debug)
1060 fprintf(stderr, "*** Glob(%s,0x%x,%p,%p)\n", path, flags, errfunc, pglob);
1061     switch (ut) {
1062     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1063         return ftpGlob(path, flags, errfunc, pglob);
1064         /*@notreached@*/ break;
1065     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1066     case URL_IS_PATH:
1067         path = lpath;
1068         /*@fallthrough@*/
1069     case URL_IS_UNKNOWN:
1070         break;
1071     case URL_IS_DASH:
1072     default:
1073         return -2;
1074         /*@notreached@*/ break;
1075     }
1076     return glob(path, flags, errfunc, pglob);
1077 }
1078
1079 void Globfree(glob_t *pglob)
1080 {
1081 if (_rpmio_debug)
1082 fprintf(stderr, "*** Globfree(%p)\n", pglob);
1083     if (pglob->gl_offs == -1) /* XXX HACK HACK HACK */
1084         ftpGlobfree(pglob);
1085     else
1086         globfree(pglob);
1087 }
1088
1089 DIR * Opendir(const char * path)
1090 {
1091     const char * lpath;
1092     int ut = urlPath(path, &lpath);
1093
1094 if (_rpmio_debug)
1095 fprintf(stderr, "*** Opendir(%s)\n", path);
1096     switch (ut) {
1097     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1098     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1099     case URL_IS_PATH:
1100         path = lpath;
1101         /*@fallthrough@*/
1102     case URL_IS_UNKNOWN:
1103         break;
1104     case URL_IS_DASH:
1105     default:
1106         return NULL;
1107         /*@notreached@*/ break;
1108     }
1109     return opendir(path);
1110 }
1111
1112 struct dirent * Readdir(DIR * dir)
1113 {
1114 if (_rpmio_debug)
1115 fprintf(stderr, "*** Readdir(%p)\n", dir);
1116     return readdir(dir);
1117 }
1118
1119 int Closedir(DIR * dir)
1120 {
1121 if (_rpmio_debug)
1122 fprintf(stderr, "*** Closedir(%p)\n", dir);
1123     return closedir(dir);
1124 }