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