- yet more boring lclint annotations and fiddles.
[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             !xstrncasecmp(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             !xstrncasecmp(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 (const char * str, /*@out@*/ struct tm * tim)
287 {
288 /*@observer@*/ static const char * week = "SunMonTueWedThuFriSat";
289     const char * pos;
290
291     /*@-observertrans -mayaliasunique@*/
292     if ((pos=strstr(week, str)) != NULL) {
293     /*@=observertrans =mayaliasunique@*/
294         if (tim != NULL)
295             tim->tm_wday = (pos - week)/3;
296         return 1;
297     }
298     return 0;    
299 }
300
301 static int
302 is_month (const char * str, /*@out@*/ struct tm * tim)
303 {
304 /*@observer@*/ static const char * month = "JanFebMarAprMayJunJulAugSepOctNovDec";
305     const char * pos;
306     
307     /*@-observertrans -mayaliasunique@*/
308     if ((pos=strstr(month, str)) != NULL) {
309     /*@=observertrans -mayaliasunique@*/
310         if (tim != NULL)
311             tim->tm_mon = (pos - month)/3;
312         return 1;
313     }
314     return 0;
315 }
316
317 static int
318 is_time (const char * str, /*@out@*/ struct tm * tim)
319 {
320     const char * p, * p2;
321
322     if ((p=strchr(str, ':')) && (p2=strrchr(str, ':'))) {
323         if (p != p2) {
324             if (sscanf (str, "%2d:%2d:%2d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec) != 3)
325                 return 0;
326         }
327         else {
328             if (sscanf (str, "%2d:%2d", &tim->tm_hour, &tim->tm_min) != 2)
329                 return 0;
330         }
331     }
332     else 
333         return 0;
334     
335     return 1;
336 }
337
338 static int is_year(const char * str, /*@out@*/ struct tm * tim)
339 {
340     long year;
341
342     if (strchr(str,':'))
343         return 0;
344
345     if (strlen(str)!=4)
346         return 0;
347
348     if (sscanf(str, "%ld", &year) != 1)
349         return 0;
350
351     if (year < 1900 || year > 3000)
352         return 0;
353
354     tim->tm_year = (int) (year - 1900);
355
356     return 1;
357 }
358
359 /*
360  * FIXME: this is broken. Consider following entry:
361  * -rwx------   1 root     root            1 Aug 31 10:04 2904 1234
362  * where "2904 1234" is filename. Well, this code decodes it as year :-(.
363  */
364
365 static int
366 vfs_parse_filetype (char c)
367 {
368     switch (c) {
369         case 'd': return S_IFDIR; 
370         case 'b': return S_IFBLK;
371         case 'c': return S_IFCHR;
372         case 'l': return S_IFLNK;
373         case 's':
374 #ifdef IS_IFSOCK /* And if not, we fall through to IFIFO, which is pretty close */
375                   return S_IFSOCK;
376 #endif
377         case 'p': return S_IFIFO;
378         case 'm': case 'n':             /* Don't know what these are :-) */
379         case '-': case '?': return S_IFREG;
380         default: return -1;
381     }
382 }
383
384 static int vfs_parse_filemode (const char *p)
385 {       /* converts rw-rw-rw- into 0666 */
386     int res = 0;
387     switch (*(p++)) {
388         case 'r': res |= 0400; break;
389         case '-': break;
390         default: return -1;
391     }
392     switch (*(p++)) {
393         case 'w': res |= 0200; break;
394         case '-': break;
395         default: return -1;
396     }
397     switch (*(p++)) {
398         case 'x': res |= 0100; break;
399         case 's': res |= 0100 | S_ISUID; break;
400         case 'S': res |= S_ISUID; break;
401         case '-': break;
402         default: return -1;
403     }
404     switch (*(p++)) {
405         case 'r': res |= 0040; break;
406         case '-': break;
407         default: return -1;
408     }
409     switch (*(p++)) {
410         case 'w': res |= 0020; break;
411         case '-': break;
412         default: return -1;
413     }
414     switch (*(p++)) {
415         case 'x': res |= 0010; break;
416         case 's': res |= 0010 | S_ISGID; break;
417         case 'l': /* Solaris produces these */
418         case 'S': res |= S_ISGID; break;
419         case '-': break;
420         default: return -1;
421     }
422     switch (*(p++)) {
423         case 'r': res |= 0004; break;
424         case '-': break;
425         default: return -1;
426     }
427     switch (*(p++)) {
428         case 'w': res |= 0002; break;
429         case '-': break;
430         default: return -1;
431     }
432     switch (*(p++)) {
433         case 'x': res |= 0001; break;
434         case 't': res |= 0001 | S_ISVTX; break;
435         case 'T': res |= S_ISVTX; break;
436         case '-': break;
437         default: return -1;
438     }
439     return res;
440 }
441
442 static int vfs_parse_filedate(int idx, time_t *t)
443 {       /* This thing parses from idx in columns[] array */
444
445     char *p;
446     struct tm tim;
447     int d[3];
448     int got_year = 0;
449
450     /* Let's setup default time values */
451     tim.tm_year = current_year;
452     tim.tm_mon  = current_mon;
453     tim.tm_mday = current_mday;
454     tim.tm_hour = 0;
455     tim.tm_min  = 0;
456     tim.tm_sec  = 0;
457     tim.tm_isdst = -1; /* Let mktime() try to guess correct dst offset */
458     
459     p = columns [idx++];
460     
461     /* We eat weekday name in case of extfs */
462     if(is_week(p, &tim))
463         p = columns [idx++];
464
465     /* Month name */
466     if(is_month(p, &tim)){
467         /* And we expect, it followed by day number */
468         if (is_num (idx))
469             tim.tm_mday = (int)atol (columns [idx++]);
470         else
471             return 0; /* No day */
472
473     } else {
474         /* We usually expect:
475            Mon DD hh:mm
476            Mon DD  YYYY
477            But in case of extfs we allow these date formats:
478            Mon DD YYYY hh:mm
479            Mon DD hh:mm YYYY
480            Wek Mon DD hh:mm:ss YYYY
481            MM-DD-YY hh:mm
482            where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
483            YYYY four digit year, hh, mm, ss two digit hour, minute or second. */
484
485         /* Here just this special case with MM-DD-YY */
486         if (is_dos_date(p)){
487             p[2] = p[5] = '-';
488             
489             memset(d, 0, sizeof(d));
490             if (sscanf(p, "%2d-%2d-%2d", &d[0], &d[1], &d[2]) == 3){
491             /*  We expect to get:
492                 1. MM-DD-YY
493                 2. DD-MM-YY
494                 3. YY-MM-DD
495                 4. YY-DD-MM  */
496                 
497                 /* Hmm... maybe, next time :)*/
498                 
499                 /* At last, MM-DD-YY */
500                 d[0]--; /* Months are zerobased */
501                 /* Y2K madness */
502                 if(d[2] < 70)
503                     d[2] += 100;
504
505                 tim.tm_mon  = d[0];
506                 tim.tm_mday = d[1];
507                 tim.tm_year = d[2];
508                 got_year = 1;
509             } else
510                 return 0; /* sscanf failed */
511         } else
512             return 0; /* unsupported format */
513     }
514
515     /* Here we expect to find time and/or year */
516     
517     if (is_num (idx)) {
518         if(is_time(columns[idx], &tim) || (got_year = is_year(columns[idx], &tim))) {
519         idx++;
520
521         /* This is a special case for ctime() or Mon DD YYYY hh:mm */
522         if(is_num (idx) && 
523             ((got_year = is_year(columns[idx], &tim)) || is_time(columns[idx], &tim)))
524                 idx++; /* time & year or reverse */
525         } /* only time or date */
526     }
527     else 
528         return 0; /* Nor time or date */
529
530     /*
531      * If the date is less than 6 months in the past, it is shown without year
532      * other dates in the past or future are shown with year but without time
533      * This does not check for years before 1900 ... I don't know, how
534      * to represent them at all
535      */
536     if (!got_year &&
537         current_mon < 6 && current_mon < tim.tm_mon && 
538         tim.tm_mon - current_mon >= 6)
539
540         tim.tm_year--;
541
542     if ((*t = mktime(&tim)) < 0)
543         *t = 0;
544     return idx;
545 }
546
547 static int
548 vfs_parse_ls_lga (char * p, /*@out@*/ struct stat * st,
549                 /*@out@*/ const char ** filename,
550                 /*@out@*/ const char ** linkname)
551 {
552     int idx, idx2, num_cols;
553     int i;
554     char *p_copy;
555     
556     if (strncmp (p, "total", 5) == 0)
557         return 0;
558
559     p_copy = g_strdup(p);
560 /* XXX FIXME: parse out inode number from "NLST -lai ." */
561 /* XXX FIXME: parse out sizein blocks from "NLST -lais ." */
562
563     if ((i = vfs_parse_filetype(*(p++))) == -1)
564         goto error;
565
566     st->st_mode = i;
567     if (*p == ' ')      /* Notwell 4 */
568         p++;
569     if (*p == '['){
570         if (strlen (p) <= 8 || p [8] != ']')
571             goto error;
572         /* Should parse here the Notwell permissions :) */
573         /*@-unrecog@*/
574         if (S_ISDIR (st->st_mode))
575             st->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH);
576         else
577             st->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
578         p += 9;
579         /*@=unrecog@*/
580     } else {
581         if ((i = vfs_parse_filemode(p)) == -1)
582             goto error;
583         st->st_mode |= i;
584         p += 9;
585
586         /* This is for an extra ACL attribute (HP-UX) */
587         if (*p == '+')
588             p++;
589     }
590
591     g_free(p_copy);
592     p_copy = g_strdup(p);
593     num_cols = vfs_split_text (p);
594
595     st->st_nlink = atol (columns [0]);
596     if (st->st_nlink < 0)
597         goto error;
598
599     if (!is_num (1))
600 #ifdef  HACK
601         st->st_uid = finduid (columns [1]);
602 #else
603         (void) unameToUid (columns [1], &st->st_uid);
604 #endif
605     else
606         st->st_uid = (uid_t) atol (columns [1]);
607
608     /* Mhm, the ls -lg did not produce a group field */
609     for (idx = 3; idx <= 5; idx++) 
610         if (is_month(columns [idx], NULL) || is_week(columns [idx], NULL) || is_dos_date(columns[idx]))
611             break;
612
613     if (idx == 6 || (idx == 5 && !S_ISCHR (st->st_mode) && !S_ISBLK (st->st_mode)))
614         goto error;
615
616     /* We don't have gid */     
617     if (idx == 3 || (idx == 4 && (S_ISCHR(st->st_mode) || S_ISBLK (st->st_mode))))
618         idx2 = 2;
619     else { 
620         /* We have gid field */
621         if (is_num (2))
622             st->st_gid = (gid_t) atol (columns [2]);
623         else
624 #ifdef  HACK
625             st->st_gid = findgid (columns [2]);
626 #else
627             (void) gnameToGid (columns [1], &st->st_gid);
628 #endif
629         idx2 = 3;
630     }
631
632     /* This is device */
633     if (S_ISCHR (st->st_mode) || S_ISBLK (st->st_mode)){
634         int maj, min;
635         
636         if (!is_num (idx2) || sscanf(columns [idx2], " %d,", &maj) != 1)
637             goto error;
638         
639         if (!is_num (++idx2) || sscanf(columns [idx2], " %d", &min) != 1)
640             goto error;
641         
642 #ifdef HAVE_ST_RDEV
643         st->st_rdev = ((maj & 0xff) << 8) | (min & 0xffff00ff);
644 #endif
645         st->st_size = 0;
646         
647     } else {
648         /* Common file size */
649         if (!is_num (idx2))
650             goto error;
651         
652         st->st_size = (size_t) atol (columns [idx2]);
653 #ifdef HAVE_ST_RDEV
654         st->st_rdev = 0;
655 #endif
656     }
657
658     idx = vfs_parse_filedate(idx, &st->st_mtime);
659     if (!idx)
660         goto error;
661     /* Use resulting time value */
662     st->st_atime = st->st_ctime = st->st_mtime;
663     st->st_dev = 0;
664     st->st_ino = 0;
665 #ifdef HAVE_ST_BLKSIZE
666     st->st_blksize = 512;
667 #endif
668 #ifdef HAVE_ST_BLOCKS
669     st->st_blocks = (st->st_size + 511) / 512;
670 #endif
671
672     for (i = idx + 1, idx2 = 0; i < num_cols; i++ ) 
673         if (strcmp (columns [i], "->") == 0){
674             idx2 = i;
675             break;
676         }
677     
678     if (((S_ISLNK (st->st_mode) || 
679         (num_cols == idx + 3 && st->st_nlink > 1))) /* Maybe a hardlink? (in extfs) */
680         && idx2){
681         int tlen;
682         char *t;
683             
684         if (filename){
685 #ifdef HACK
686             t = g_strndup (p_copy + column_ptr [idx], column_ptr [idx2] - column_ptr [idx] - 1);
687 #else
688             int nb = column_ptr [idx2] - column_ptr [idx] - 1;
689             t = xmalloc(nb+1);
690             strncpy(t, p_copy + column_ptr [idx], nb);
691 #endif
692             *filename = t;
693         }
694         if (linkname){
695             t = g_strdup (p_copy + column_ptr [idx2+1]);
696             tlen = strlen (t);
697             if (t [tlen-1] == '\r' || t [tlen-1] == '\n')
698                 t [tlen-1] = 0;
699             if (t [tlen-2] == '\r' || t [tlen-2] == '\n')
700                 t [tlen-2] = 0;
701                 
702             *linkname = t;
703         }
704     } else {
705         /* Extract the filename from the string copy, not from the columns
706          * this way we have a chance of entering hidden directories like ". ."
707          */
708         if (filename){
709             /* 
710             *filename = g_strdup (columns [idx++]);
711             */
712             int tlen;
713             char *t;
714             
715             t = g_strdup (p_copy + column_ptr [idx++]);
716             tlen = strlen (t);
717             /* g_strchomp(); */
718             if (t [tlen-1] == '\r' || t [tlen-1] == '\n')
719                 t [tlen-1] = 0;
720             if (t [tlen-2] == '\r' || t [tlen-2] == '\n')
721                 t [tlen-2] = 0;
722             
723             *filename = t;
724         }
725         if (linkname)
726             *linkname = NULL;
727     }
728     g_free (p_copy);
729     return 1;
730
731 error:
732 #ifdef  HACK
733     {
734       static int errorcount = 0;
735
736       if (++errorcount < 5) {
737         message_1s (1, "Could not parse:", p_copy);
738       } else if (errorcount == 5)
739         message_1s (1, "More parsing errors will be ignored.", "(sorry)" );
740     }
741 #endif
742
743     /*@-usereleased@*/
744     if (p_copy != p)            /* Carefull! */
745     /*@=usereleased@*/
746         g_free (p_copy);
747     return 0;
748 }
749
750 typedef enum {
751         DO_FTP_STAT     = 1,
752         DO_FTP_LSTAT    = 2,
753         DO_FTP_READLINK = 3,
754         DO_FTP_ACCESS   = 4,
755         DO_FTP_GLOB     = 5
756 } ftpSysCall_t;
757 static size_t ftpBufAlloced = 0;
758 static /*@only@*/ char * ftpBuf = NULL;
759         
760 #define alloca_strdup(_s)       strcpy(alloca(strlen(_s)+1), (_s))
761
762 static int ftpNLST(const char * url, ftpSysCall_t ftpSysCall,
763         /*@out@*/ struct stat * st, char * rlbuf, size_t rlbufsiz)
764 {
765     FD_t fd;
766     const char * path;
767     int bufLength, moretodo;
768     const char *n, *ne, *o, *oe;
769     char * s;
770     char * se;
771     const char * urldn;
772     char * bn = NULL;
773     int nbn = 0;
774     urlinfo u;
775     int rc;
776
777     n = ne = o = oe = NULL;
778     (void) urlPath(url, &path);
779     if (*path == '\0')
780         return -2;
781
782     switch (ftpSysCall) {
783     case DO_FTP_GLOB:
784         fd = ftpOpen(url, 0, 0, &u);
785         if (fd == NULL || u == NULL)
786             return -1;
787
788         u->openError = ftpReq(fd, "NLST", path);
789         break;
790     default:
791         urldn = alloca_strdup(url);
792         if ((bn = strrchr(urldn, '/')) == NULL)
793             return -2;
794         else if (bn == path)
795             bn = ".";
796         else
797             *bn++ = '\0';
798         nbn = strlen(bn);
799
800         rc = ftpChdir(urldn);           /* XXX don't care about CWD */
801         if (rc < 0)
802             return rc;
803
804         fd = ftpOpen(url, 0, 0, &u);
805         if (fd == NULL || u == NULL)
806             return -1;
807
808         /* XXX possibly should do "NLST -lais" to get st_ino/st_blocks also */
809         u->openError = ftpReq(fd, "NLST", "-la");
810         break;
811     }
812
813     if (u->openError < 0) {
814         fd = fdLink(fd, "error data (ftpStat)");
815         rc = -2;
816         goto exit;
817     }
818
819     if (ftpBufAlloced == 0 || ftpBuf == NULL) {
820         ftpBufAlloced = url_iobuf_size;
821         ftpBuf = xcalloc(ftpBufAlloced, sizeof(ftpBuf[0]));
822     }
823     *ftpBuf = '\0';
824
825     bufLength = 0;
826     moretodo = 1;
827
828     do {
829
830         /* XXX FIXME: realloc ftpBuf is < ~128 chars remain */
831         if ((ftpBufAlloced - bufLength) < (1024+80)) {
832             ftpBufAlloced <<= 2;
833             ftpBuf = xrealloc(ftpBuf, ftpBufAlloced);
834         }
835         s = se = ftpBuf + bufLength;
836         *se = '\0';
837
838         rc = fdFgets(fd, se, (ftpBufAlloced - bufLength));
839         if (rc <= 0) {
840             moretodo = 0;
841             break;
842         }
843         if (ftpSysCall == DO_FTP_GLOB) {        /* XXX HACK */
844             bufLength += strlen(se);
845             continue;
846         }
847
848         for (s = se; *s != '\0'; s = se) {
849             int bingo;
850
851             while (*se && *se != '\n') se++;
852             if (se > s && se[-1] == '\r') se[-1] = '\0';
853             if (*se == '\0') break;
854             *se++ = '\0';
855
856             if (!strncmp(s, "total ", sizeof("total ")-1)) continue;
857
858             o = NULL;
859             for (bingo = 0, n = se; n >= s; n--) {
860                 switch (*n) {
861                 case '\0':
862                     oe = ne = n;
863                     break;
864                 case ' ':
865                     if (o || !(n[-3] == ' ' && n[-2] == '-' && n[-1] == '>')) {
866                         while (*(++n) == ' ');
867                         bingo++;
868                         break;
869                     }
870                     for (o = n + 1; *o == ' '; o++);
871                     n -= 3;
872                     ne = n;
873                     break;
874                 default:
875                     break;
876                 }
877                 if (bingo) break;
878             }
879
880             if (nbn != (ne - n))        continue;       /* Same name length? */
881             if (strncmp(n, bn, nbn))    continue;       /* Same name? */
882
883             moretodo = 0;
884             break;
885         }
886
887         if (moretodo && se > s) {
888             bufLength = se - s - 1;
889             if (s != ftpBuf)
890                 memmove(ftpBuf, s, bufLength);
891         } else {
892             bufLength = 0;
893         }
894     } while (moretodo);
895
896     switch (ftpSysCall) {
897     case DO_FTP_STAT:
898         if (o && oe) {
899             /* XXX FIXME: symlink, replace urldn/bn from [o,oe) and restart */
900         }
901         /*@fallthrough@*/
902     case DO_FTP_LSTAT:
903         if (st == NULL || !(n && ne)) {
904             rc = -1;
905         } else {
906             rc = ((vfs_parse_ls_lga(s, st, NULL, NULL) > 0) ? 0 : -1);
907         }
908         break;
909     case DO_FTP_READLINK:
910         if (rlbuf == NULL || !(o && oe)) {
911             rc = -1;
912         } else {
913             rc = oe - o;
914             if (rc > rlbufsiz)
915                 rc = rlbufsiz;
916             memcpy(rlbuf, o, rc);
917             if (rc < rlbufsiz)
918                 rlbuf[rc] = '\0';
919         }
920         break;
921     case DO_FTP_ACCESS:
922         rc = 0;         /* XXX WRONG WRONG WRONG */
923         break;
924     case DO_FTP_GLOB:
925         rc = 0;         /* XXX WRONG WRONG WRONG */
926         break;
927     }
928
929 exit:
930     (void) ufdClose(fd);
931     return rc;
932 }
933
934 static int ftpStat(const char * path, /*@out@*/ struct stat *st)
935 {
936     return ftpNLST(path, DO_FTP_STAT, st, NULL, 0);
937 }
938
939 static int ftpLstat(const char * path, /*@out@*/ struct stat *st) {
940     int rc;
941     rc = ftpNLST(path, DO_FTP_LSTAT, st, NULL, 0);
942 if (_rpmio_debug)
943 fprintf(stderr, "*** ftpLstat(%s) rc %d\n", path, rc);
944     return rc;
945 }
946
947 static int ftpReadlink(const char * path, char * buf, size_t bufsiz) {
948     return ftpNLST(path, DO_FTP_READLINK, NULL, buf, bufsiz);
949 }
950
951 static int ftpGlob(const char * path, int flags,
952                 int errfunc(const char * epath, int eerno),
953                 /*@out@*/ glob_t * pglob)
954 {
955     int rc;
956
957     if (pglob == NULL)
958         return -2;
959     rc = ftpNLST(path, DO_FTP_GLOB, NULL, NULL, 0);
960 /*@-castfcnptr@*/
961 if (_rpmio_debug)
962 fprintf(stderr, "*** ftpGlob(%s,0x%x,%p,%p) ftpNLST rc %d\n", path, (unsigned)flags, (void *)errfunc, pglob, rc);
963 /*@=castfcnptr@*/
964     if (rc)
965         return rc;
966     rc = poptParseArgvString(ftpBuf, &pglob->gl_pathc, (const char ***)&pglob->gl_pathv);
967     pglob->gl_offs = -1;        /* XXX HACK HACK HACK */
968     return rc;
969 }
970
971 static void ftpGlobfree(glob_t * pglob) {
972 if (_rpmio_debug)
973 fprintf(stderr, "*** ftpGlobfree(%p)\n", pglob);
974     if (pglob->gl_offs == -1) { /* XXX HACK HACK HACK */
975         free((void *)pglob->gl_pathv);
976         pglob->gl_pathv = NULL;
977     }
978 }
979
980 int Stat(const char * path, struct stat * st) {
981     const char * lpath;
982     int ut = urlPath(path, &lpath);
983
984 if (_rpmio_debug)
985 fprintf(stderr, "*** Stat(%s,%p)\n", path, st);
986     switch (ut) {
987     case URL_IS_FTP:
988         return ftpStat(path, st);
989         /*@notreached@*/ break;
990     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
991     case URL_IS_PATH:
992         path = lpath;
993         /*@fallthrough@*/
994     case URL_IS_UNKNOWN:
995         break;
996     case URL_IS_DASH:
997     default:
998         return -2;
999         /*@notreached@*/ break;
1000     }
1001     return stat(path, st);
1002 }
1003
1004 int Lstat(const char * path, struct stat * st) {
1005     const char * lpath;
1006     int ut = urlPath(path, &lpath);
1007
1008 if (_rpmio_debug)
1009 fprintf(stderr, "*** Lstat(%s,%p)\n", path, st);
1010     switch (ut) {
1011     case URL_IS_FTP:
1012         return ftpLstat(path, st);
1013         /*@notreached@*/ break;
1014     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1015     case URL_IS_PATH:
1016         path = lpath;
1017         /*@fallthrough@*/
1018     case URL_IS_UNKNOWN:
1019         break;
1020     case URL_IS_DASH:
1021     default:
1022         return -2;
1023         /*@notreached@*/ break;
1024     }
1025     return lstat(path, st);
1026 }
1027
1028 int Readlink(const char * path, char * buf, size_t bufsiz) {
1029     const char * lpath;
1030     int ut = urlPath(path, &lpath);
1031
1032     switch (ut) {
1033     case URL_IS_FTP:
1034         return ftpReadlink(path, buf, bufsiz);
1035         /*@notreached@*/ break;
1036     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1037     case URL_IS_PATH:
1038         path = lpath;
1039         /*@fallthrough@*/
1040     case URL_IS_UNKNOWN:
1041         break;
1042     case URL_IS_DASH:
1043     default:
1044         return -2;
1045         /*@notreached@*/ break;
1046     }
1047     return readlink(path, buf, bufsiz);
1048 }
1049
1050 int Access(const char * path, int amode) {
1051     const char * lpath;
1052     int ut = urlPath(path, &lpath);
1053
1054 if (_rpmio_debug)
1055 fprintf(stderr, "*** Access(%s,%d)\n", path, amode);
1056     switch (ut) {
1057     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1058     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1059     case URL_IS_PATH:
1060         path = lpath;
1061         /*@fallthrough@*/
1062     case URL_IS_UNKNOWN:
1063         break;
1064     case URL_IS_DASH:
1065     default:
1066         return -2;
1067         /*@notreached@*/ break;
1068     }
1069     return access(path, amode);
1070 }
1071
1072 int Glob(const char *path, int flags,
1073         int errfunc(const char * epath, int eerrno), glob_t *pglob)
1074 {
1075     const char * lpath;
1076     int ut = urlPath(path, &lpath);
1077
1078 /*@-castfcnptr@*/
1079 if (_rpmio_debug)
1080 fprintf(stderr, "*** Glob(%s,0x%x,%p,%p)\n", path, (unsigned)flags, (void *)errfunc, pglob);
1081 /*@=castfcnptr@*/
1082     switch (ut) {
1083     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1084         return ftpGlob(path, flags, errfunc, pglob);
1085         /*@notreached@*/ break;
1086     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1087     case URL_IS_PATH:
1088         path = lpath;
1089         /*@fallthrough@*/
1090     case URL_IS_UNKNOWN:
1091         break;
1092     case URL_IS_DASH:
1093     default:
1094         return -2;
1095         /*@notreached@*/ break;
1096     }
1097     return glob(path, flags, errfunc, pglob);
1098 }
1099
1100 void Globfree(glob_t *pglob)
1101 {
1102 if (_rpmio_debug)
1103 fprintf(stderr, "*** Globfree(%p)\n", pglob);
1104     if (pglob->gl_offs == -1) /* XXX HACK HACK HACK */
1105         ftpGlobfree(pglob);
1106     else
1107         globfree(pglob);
1108 }
1109
1110 DIR * Opendir(const char * path)
1111 {
1112     const char * lpath;
1113     int ut = urlPath(path, &lpath);
1114
1115 if (_rpmio_debug)
1116 fprintf(stderr, "*** Opendir(%s)\n", path);
1117     switch (ut) {
1118     case URL_IS_FTP:            /* XXX WRONG WRONG WRONG */
1119     case URL_IS_HTTP:           /* XXX WRONG WRONG WRONG */
1120     case URL_IS_PATH:
1121         path = lpath;
1122         /*@fallthrough@*/
1123     case URL_IS_UNKNOWN:
1124         break;
1125     case URL_IS_DASH:
1126     default:
1127         return NULL;
1128         /*@notreached@*/ break;
1129     }
1130     return opendir(path);
1131 }
1132
1133 /*@+voidabstract@*/
1134 struct dirent * Readdir(DIR * dir)
1135 {
1136 if (_rpmio_debug)
1137 fprintf(stderr, "*** Readdir(%p)\n", (void *)dir);
1138     return readdir(dir);
1139 }
1140
1141 int Closedir(DIR * dir)
1142 {
1143 if (_rpmio_debug)
1144 fprintf(stderr, "*** Closedir(%p)\n", (void *)dir);
1145     return closedir(dir);
1146 }
1147 /*@=voidabstract@*/