3 * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
10 static const char *rwx
[9] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx", NULL
};
14 /* We need to use this because using NLST gives us more stuff than
15 * we want back sometimes. For example, say we have:
28 * If you did an "echo /a/<star>" in a normal unix shell, you would expect
29 * to get back /a/b /a/c /a/file. But NLST gives back:
39 * So we use the following routine to convert into the format I expect.
43 RemoteGlobCollapse(const char *pattern
, LineListPtr fileList
)
53 /* Copy all characters before the first glob-char. */
55 endp
= dp
+ sizeof(patPrefix
) - 1;
57 for (cp
= (char *) pattern
; dp
< endp
; ) {
58 for (pp
=kGlobChars
; *pp
!= '\0'; pp
++) {
69 plen
= (size_t) (dp
- patPrefix
);
72 for (lp
=fileList
->first
; lp
!= NULL
; lp
= nextLine
) {
74 if (strncmp(lp
->line
, patPrefix
, plen
) == 0) {
75 (void) STRNCPY(cur
, lp
->line
+ plen
);
76 cp
= strchr(cur
, '/');
78 cp
= strchr(cur
, '\\');
81 if ((*prev
!= '\0') && (STREQ(cur
, prev
))) {
82 nextLine
= RemoveLine(fileList
, lp
);
84 (void) STRNCPY(prev
, cur
);
85 /* We are playing with a dynamically
86 * allocated string, but since the
87 * following expression is guaranteed
88 * to be the same or shorter, we won't
89 * overwrite the bounds.
91 (void) sprintf(lp
->line
, "%s%s", patPrefix
, cur
);
95 } /* RemoteGlobCollapse */
101 /* May need this later. */
103 CheckForLS_d(FTPCIPtr cip
)
108 if (cip
->hasNLST_d
== kCommandAvailabilityUnknown
) {
109 if (FTPListToMemory2(cip
, ".", &lines
, "-d ", 0, (int *) 0) == kNoErr
) {
110 if ((lines
.first
!= NULL
) && (lines
.first
== lines
.last
)) {
111 /* If we have only one item in the list, see if it really was
112 * an error message we would recognize.
114 cp
= strchr(lines
.first
->line
, ':');
115 if ((cp
!= NULL
) && STREQ(cp
, ": No such file or directory")) {
116 cip
->hasNLST_d
= kCommandNotAvailable
;
118 cip
->hasNLST_d
= kCommandAvailable
;
121 cip
->hasNLST_d
= kCommandNotAvailable
;
124 cip
->hasNLST_d
= kCommandNotAvailable
;
126 DisposeLineListContents(&lines
);
135 LsMonthNameToNum(char *cp
)
141 mon
= (*cp
== 'u') ? 7 : 3;
152 mon
= (*cp
== 'l') ? 6 : 5;
157 mon
= (*++cp
== 'r') ? 2 : 4;
169 } /* LsMonthNameToNum */
175 UnDosLine( char *const line
,
176 const char *const curdir
,
192 0123456789012345678901234567890123456789012345678901234567890123456789
193 04-27-99 10:32PM 270158 Game booklet.pdf
194 03-11-99 10:03PM <DIR> Get A3d Banner
196 We also try to parse the format from CMD.EXE, which is similar:
198 03/22/2001 06:23p 62,325 cls.pdf
205 && isdigit((int) cp
[1])
206 && ispunct((int) cp
[2])
207 && isdigit((int) cp
[3])
208 && isdigit((int) cp
[4])
209 && ispunct((int) cp
[5])
210 && isdigit((int) cp
[6])
211 && isdigit((int) cp
[7])
213 (void) memset(&ftm
, 0, sizeof(struct tm
));
216 ftm
.tm_mon
= atoi(cp
+ 0);
220 ftm
.tm_mday
= atoi(cp
+ 3);
221 if ((isdigit((int) cp
[8])) && (isdigit((int) cp
[9]))) {
222 /* Four-digit year */
227 ftm
.tm_year
= year
; /* years since 1900 */
235 ftm
.tm_year
= year
; /* years since 1900 */
249 if (((cp
[5] == 'P') || (cp
[5] == 'p')) && (hour
< 12))
251 else if (((cp
[5] == 'A') || (cp
[5] == 'a')) && (hour
== 12))
255 ftm
.tm_min
= atoi(cp
+ 3);
256 *ftime
= mktime(&ftm
);
257 if (*ftype
== (time_t) -1)
265 if ((*cp
== '<') && (cp
[1] == 'D')) {
269 break; /* size field will end up being empty string */
270 } else if ((*cp
== '<') && (cp
[1] == 'J')) {
273 * Will we ever really see this?
274 * IIS from Win2000sp1 sends <DIR>
275 * for FTP, but CMD.EXE prints
281 } else if (isdigit(*cp
)) {
294 /* Yuck -- US Locale dependency */
295 memmove(cp
, cp
+ 1, strlen(cp
+ 1) + 1);
306 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
310 (void) sscanf(sizestart
, SCANF_LONG_LONG
, fsize
);
311 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
315 *fsize
= (longest_int
) strtoq(sizestart
, NULL
, 0);
317 *fsize
= (longest_int
) 0;
321 (void) sscanf(sizestart
, "%ld", &fsize2
);
322 *fsize
= (longest_int
) fsize2
;
337 if (curdirlen
== 0) {
338 (void) Strncpy(fname
, filestart
, fnamesize
);
340 (void) Strncpy(fname
, curdir
, fnamesize
);
341 (void) Strncat(fname
, filestart
, fnamesize
);
353 UnLslRLine( char *const line
,
354 const char *const curdir
,
368 int mon
= 0, dd
= 0, hr
= 0, min
= 0, year
= 0;
369 char *monstart
, *ddstart
, *hrstart
, *minstart
, *yearstart
;
370 char *linktostart
, *filestart
= NULL
;
376 * Look for the digit just before the space
377 * before the month name.
379 -rw-rw---- 1 gleason sysdev 33404 Mar 24 01:29 RCmd.o
380 -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README
381 -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README
382 -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README
383 -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README
385 *------------------------------^
387 *------plugend--------^
391 for (cp
= line
; *cp
!= '\0'; cp
++) {
392 if ( (isdigit((int) *cp
))
393 && (isspace((int) cp
[1]))
394 && (isupper((int) cp
[2]))
395 && (islower((int) cp
[3]))
396 /* && (islower((int) cp[4])) */
397 && (isspace((int) cp
[5]))
399 ((isdigit((int) cp
[6])) && (isdigit((int) cp
[7])))
400 || ((isdigit((int) cp
[6])) && (isspace((int) cp
[7])))
401 || ((isspace((int) cp
[6])) && (isdigit((int) cp
[7])))
403 && (isspace((int) cp
[8]))
407 if ( ((isspace((int) cp
[9])) || (isdigit((int) cp
[9])))
408 && (isdigit((int) cp
[10]))
409 && (isdigit((int) cp
[11]))
410 && (isdigit((int) cp
[12]))
411 && ((isdigit((int) cp
[13])) || (isspace((int) cp
[13])))
413 /* "Mon DD YYYY" form */
415 if (isspace((int) *yearstart
))
420 cp
[1] = '\0'; /* end size */
421 cp
[5] = '\0'; /* end mon */
422 cp
[8] = '\0'; /* end dd */
423 cp
[14] = '\0'; /* end year */
424 mon
= LsMonthNameToNum(monstart
);
428 year
= atoi(yearstart
);
431 while (isdigit((int) *pe
))
433 while (isspace((int) *pe
))
435 *plugend
= (int) (pe
- line
) + 1;
438 * Windows NT does not 0 pad.
439 (isdigit((int) cp[9])) &&
441 (isdigit((int) cp
[10]))
443 && (isdigit((int) cp
[12]))
444 && (isdigit((int) cp
[13]))
446 /* "Mon DD HH:MM" form */
451 cp
[1] = '\0'; /* end size */
452 cp
[5] = '\0'; /* end mon */
453 cp
[8] = '\0'; /* end dd */
454 cp
[11] = '\0'; /* end hr */
455 cp
[14] = '\0'; /* end min */
456 mon
= LsMonthNameToNum(monstart
);
459 min
= atoi(minstart
);
463 while (isdigit((int) *pe
))
465 while (isspace((int) *pe
))
467 *plugend
= (int) (pe
- line
) + 1;
476 linktostart
= strstr(filestart
, " -> ");
477 if (linktostart
!= NULL
) {
480 (void) Strncpy(linkto
, linktostart
, linktosize
);
485 if (curdirlen
== 0) {
486 (void) Strncpy(fname
, filestart
, fnamesize
);
488 (void) Strncpy(fname
, curdir
, fnamesize
);
489 (void) Strncat(fname
, filestart
, fnamesize
);
493 (void) memset(&ftm
, 0, sizeof(struct tm
));
500 /* We guess the year, based on what the
501 * current year is. We know the file
502 * on the remote server is either less
503 * than six months old or less than
504 * one hour into the future.
506 ftm
.tm_year
= thisyear
- 1900;
507 *ftime
= mktime(&ftm
);
508 if (*ftime
== (time_t) -1) {
510 } else if (*ftime
> (now
+ (15552000L + 86400L))) {
512 *ftime
= mktime(&ftm
);
513 } else if (*ftime
< (now
- (15552000L + 86400L))) {
515 *ftime
= mktime(&ftm
);
518 ftm
.tm_year
= year
- 1900;
519 *ftime
= mktime(&ftm
);
524 while ((cp
> line
) && (isdigit((int) *cp
)))
527 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
528 (void) sscanf(sizestart
, SCANF_LONG_LONG
, fsize
);
529 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
530 *fsize
= (longest_int
) strtoq(sizestart
, NULL
, 0);
535 (void) sscanf(sizestart
, "%ld", &fsize2
);
536 *fsize
= (longest_int
) fsize2
;
544 *ftype
= (int) line
[0];
549 *ftype
= (int) line
[0];
561 UnLslR(FileInfoListPtr filp
, LineListPtr llp
, int serverType
)
565 int hadblankline
= 0;
567 size_t curdirlen
= 0;
580 int linesconverted
= 0;
581 size_t maxFileLen
= 0;
582 size_t maxPlugLen
= 0;
587 nowtm
= localtime(&now
);
589 thisyear
= 1970; /* should never happen */
591 thisyear
= nowtm
->tm_year
+ 1900;
595 InitFileInfoList(filp
);
596 for (lp
= llp
->first
; lp
!= NULL
; lp
= lp
->next
) {
597 len
= (int) strlen(STRNCPY(line
, lp
->line
));
598 if ((line
[0] == 't') && (strncmp(line
, "total", 5) == 0)) {
600 if (line
[len
- 1] != ':') {
604 /* else it was a subdir named total */
606 for (cp
= line
; ; cp
++) {
607 if ((*cp
== '\0') || (!isspace((int) *cp
)))
611 /* Entire line was blank. */
612 /* separator line between dirs */
618 if ((hadblankline
!= 0) && (line
[len
- 1] == ':')) {
621 if ((line
[0] == '.') && (line
[1] == '/')) {
623 (void) memcpy(curdir
, line
+ 2, (size_t) len
+ 1 - 2);
624 curdirlen
= (size_t) (len
- 2);
625 } else if ((line
[0] == '.') && (line
[1] == '\\')) {
626 line
[len
- 1] = '\\';
627 (void) memcpy(curdir
, line
+ 2, (size_t) len
+ 1 - 2);
628 curdirlen
= (size_t) (len
- 2);
631 (void) memcpy(curdir
, line
, (size_t) len
+ 1);
632 curdirlen
= (size_t) len
;
638 rc
= UnLslRLine(line
, curdir
, curdirlen
, fname
, sizeof(fname
), linkto
, sizeof(linkto
), &ftype
, &fsize
, &ftime
, now
, thisyear
, &plugend
);
639 if ((rc
< 0) && (serverType
== kServerTypeMicrosoftFTP
)) {
640 rc
= UnDosLine(line
, curdir
, curdirlen
, fname
, sizeof(fname
), &ftype
, &fsize
, &ftime
);
648 fileLen
= strlen(fname
);
649 if (fileLen
> maxFileLen
)
650 maxFileLen
= fileLen
;
651 fi
.relnameLen
= fileLen
;
652 fi
.relname
= StrDup(fname
);
655 fi
.rlinkto
= (linkto
[0] == '\0') ? NULL
: StrDup(linkto
);
657 fi
.size
= (longest_int
) fsize
;
660 fi
.plug
= (char *) malloc((size_t) plugend
+ 1);
661 if (fi
.plug
!= NULL
) {
662 (void) memcpy(fi
.plug
, line
, (size_t) plugend
);
663 fi
.plug
[plugend
] = '\0';
664 if ((size_t) plugend
> maxPlugLen
)
665 maxPlugLen
= (size_t) plugend
;
668 fi
.plug
= (char *) malloc(32);
669 if (fi
.plug
!= NULL
) {
670 strcpy(fi
.plug
, "---------- 1 ftpuser ftpusers");
671 fi
.plug
[0] = (char) ftype
;
673 maxPlugLen
= (size_t) 30;
676 (void) AddFileInfo(filp
, &fi
);
682 filp
->maxFileLen
= maxFileLen
;
683 filp
->maxPlugLen
= maxPlugLen
;
686 return ((linesconverted
> 0) ? linesconverted
: (-1));
693 UnMlsT(const char *const line0
, const MLstItemPtr mlip
)
695 char *cp
, *val
, *fact
;
700 memset(mlip
, 0, sizeof(MLstItem
));
702 mlip
->fsize
= kSizeUnknown
;
704 mlip
->ftime
= kModTimeUnknown
;
707 if (len
> (sizeof(line
) - 1))
708 return (-1); /* Line too long, sorry. */
709 /* This should be re-coded so does not need to make a
710 * copy of the buffer; it could be done in place.
712 memcpy(line
, line0
, len
+ 1);
714 /* Skip leading whitespace. */
715 for (cp
= line
; *cp
!= '\0'; cp
++) {
720 while (*cp
!= '\0') {
721 for (fact
= cp
; ; cp
++) {
722 if ((*cp
== '\0') || (*cp
== ' ')) {
723 /* protocol violation */
727 /* End of fact name. */
732 for (val
= cp
; ; cp
++) {
734 /* protocol violation */
741 } else if (*cp
== ';') {
753 if (ISTRNEQ(fact
, "OS.", 3))
755 if (ISTREQ(fact
, "type")) {
756 if (ISTREQ(val
, "file")) {
758 } else if (ISTREQ(val
, "dir")) {
760 } else if (ISTREQ(val
, "cdir")) {
761 /* not supported: current directory */
763 } else if (ISTREQ(val
, "pdir")) {
764 /* not supported: parent directory */
770 } else if (ISTREQ(fact
, "UNIX.mode")) {
772 sscanf(val
, "%o", &mlip
->mode
);
774 sscanf(val
, "%i", &mlip
->mode
);
775 if (mlip
->mode
!= (-1))
777 } else if (ISTREQ(fact
, "perm")) {
778 STRNCPY(mlip
->perm
, val
);
779 } else if (ISTREQ(fact
, "size")) {
780 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
781 (void) sscanf(val
, SCANF_LONG_LONG
, &mlip
->fsize
);
782 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
783 mlip
->fsize
= (longest_int
) strtoq(val
, NULL
, 0);
788 (void) sscanf(val
, "%ld", &fsize2
);
789 mlip
->fsize
= (longest_int
) fsize2
;
792 } else if (ISTREQ(fact
, "modify")) {
793 mlip
->ftime
= UnMDTMDate(val
);
794 } else if (ISTREQ(fact
, "UNIX.owner")) {
795 STRNCPY(mlip
->owner
, val
);
796 } else if (ISTREQ(fact
, "UNIX.group")) {
797 STRNCPY(mlip
->group
, val
);
798 } else if (ISTREQ(fact
, "UNIX.uid")) {
799 mlip
->uid
= atoi(val
);
800 } else if (ISTREQ(fact
, "UNIX.gid")) {
801 mlip
->gid
= atoi(val
);
802 } else if (ISTREQ(fact
, "perm")) {
803 STRNCPY(mlip
->perm
, val
);
812 if (len
> (sizeof(mlip
->fname
) - 1)) {
813 /* Filename too long */
816 memcpy(mlip
->fname
, cp
, len
);
818 /* also set linkto here if used */
827 UnMlsD(FileInfoListPtr filp
, LineListPtr llp
)
836 int linesconverted
= 0;
837 int linesignored
= 0;
838 size_t maxFileLen
= 0;
839 size_t maxPlugLen
= 0;
840 size_t fileLen
, plugLen
;
842 const char *cm1
, *cm2
, *cm3
;
844 InitFileInfoList(filp
);
845 for (lp
= llp
->first
; lp
!= NULL
; lp
= lp
->next
) {
847 rc
= UnMlsT(lp
->line
, &mli
);
850 fileLen
= strlen(mli
.fname
);
851 if (fileLen
> maxFileLen
)
852 maxFileLen
= fileLen
;
853 fi
.relnameLen
= fileLen
;
854 fi
.relname
= StrDup(mli
.fname
);
857 fi
.rlinkto
= (mli
.linkto
[0] == '\0') ? NULL
: StrDup(mli
.linkto
);
859 fi
.size
= (longest_int
) mli
.fsize
;
861 plug
[0] = (char) mli
.ftype
;
866 if (mli
.mode
!= (-1)) {
867 m1
= (mli
.mode
& 00700) >> 6;
868 m2
= (mli
.mode
& 00070) >> 3;
869 m3
= (mli
.mode
& 00007);
871 if (mli
.perm
[0] != '\0') {
873 if (fi
.type
== 'd') {
874 if (strchr(mli
.perm
, 'e') != NULL
) {
875 /* execute -> execute */
878 if (strchr(mli
.perm
, 'c') != NULL
) {
879 /* create -> write */
882 if (strchr(mli
.perm
, 'l') != NULL
) {
887 if (strchr(mli
.perm
, 'w') != NULL
) {
891 if (strchr(mli
.perm
, 'r') != NULL
) {
901 sprintf(plug
+ 1, "%s%s%s", cm1
, cm2
, cm3
);
903 if (mli
.owner
[0] != '\0') {
904 if (mli
.group
[0] != '\0') {
906 snprintf(og
, sizeof(og
) - 1,
909 #endif /* HAVE_SNPRINTF */
916 STRNCAT(plug
, mli
.owner
);
919 fi
.plug
= StrDup(plug
);
920 if (fi
.plug
!= NULL
) {
921 plugLen
= strlen(plug
);
922 if (plugLen
> maxPlugLen
)
923 maxPlugLen
= plugLen
;
925 (void) AddFileInfo(filp
, &fi
);
926 } else if (rc
== (-2)) {
931 filp
->maxFileLen
= maxFileLen
;
932 filp
->maxPlugLen
= maxPlugLen
;
935 linesconverted
+= linesignored
;
936 return ((linesconverted
> 0) ? linesconverted
: (-1));
943 print1(FileInfoListPtr list
)
948 for (i
= 1, fip
= list
->first
; fip
!= NULL
; fip
= fip
->next
, i
++)
949 printf("%d: %s\n", i
, fip
->relname
== NULL
? "NULL" : fip
->relname
);
955 print2(FileInfoListPtr list
)
960 n
= list
->nFileInfos
;
961 for (i
=0; i
<n
; i
++) {
963 printf("%d: %s\n", i
+ 1, fip
->relname
== NULL
? "NULL" : fip
->relname
);
971 SortRecursiveFileList(FileInfoListPtr files
)
973 VectorizeFileInfoList(files
);
974 SortFileInfoList(files
, 'b', '?');
975 UnvectorizeFileInfoList(files
);
976 } /* SortRecursiveFileList */
983 FTPRemoteRecursiveFileList1(FTPCIPtr cip
, char *const rdir
, FileInfoListPtr files
)
985 LineList dirContents
;
990 if ((result
= FTPGetCWD(cip
, cwd
, sizeof(cwd
))) < 0)
993 InitFileInfoList(files
);
998 if (FTPChdir(cip
, rdir
) < 0) {
999 /* Probably not a directory.
1000 * Just add it as a plain file
1003 (void) ConcatFileToFileInfoList(files
, rdir
);
1007 /* Paths collected must be relative. */
1008 if ((result
= FTPListToMemory2(cip
, "", &dirContents
, "-lRa", 1, (int *) 0)) < 0) {
1009 if ((result
= FTPChdir(cip
, cwd
)) < 0) {
1014 (void) UnLslR(&fil
, &dirContents
, cip
->serverType
);
1015 DisposeLineListContents(&dirContents
);
1016 /* Could sort it to breadth-first here. */
1017 /* (void) SortRecursiveFileList(&fil); */
1018 (void) ComputeRNames(&fil
, rdir
, 1, 1);
1019 (void) ConcatFileInfoList(files
, &fil
);
1020 DisposeFileInfoListContents(&fil
);
1022 if ((result
= FTPChdir(cip
, cwd
)) < 0) {
1026 } /* FTPRemoteRecursiveFileList1 */
1032 FTPRemoteRecursiveFileList(FTPCIPtr cip
, LineListPtr fileList
, FileInfoListPtr files
)
1034 LinePtr filePtr
, nextFilePtr
;
1035 LineList dirContents
;
1041 if ((result
= FTPGetCWD(cip
, cwd
, sizeof(cwd
))) < 0)
1044 InitFileInfoList(files
);
1046 for (filePtr
= fileList
->first
;
1048 filePtr
= nextFilePtr
)
1050 nextFilePtr
= filePtr
->next
;
1052 rdir
= filePtr
->line
;
1056 if (FTPChdir(cip
, rdir
) < 0) {
1057 /* Probably not a directory.
1058 * Just add it as a plain file
1061 (void) ConcatFileToFileInfoList(files
, rdir
);
1065 /* Paths collected must be relative. */
1066 if ((result
= FTPListToMemory2(cip
, "", &dirContents
, "-lRa", 1, (int *) 0)) < 0) {
1070 (void) UnLslR(&fil
, &dirContents
, cip
->serverType
);
1071 DisposeLineListContents(&dirContents
);
1072 (void) ComputeRNames(&fil
, rdir
, 1, 1);
1073 (void) ConcatFileInfoList(files
, &fil
);
1074 DisposeFileInfoListContents(&fil
);
1077 if ((result
= FTPChdir(cip
, cwd
)) < 0) {
1082 } /* FTPRemoteRecursiveFileList */
1086 #if defined(WIN32) || defined(_WINDOWS)
1089 Traverse(FTPCIPtr cip
, char *fullpath
, struct Stat
*st
, char *relpath
, FileInfoListPtr filp
)
1091 WIN32_FIND_DATA ffd
;
1092 HANDLE searchHandle
;
1098 /* Handle directory entry first. */
1099 if (relpath
[0] != '\0') {
1100 fi
.relname
= StrDup(relpath
);
1102 fi
.lname
= StrDup(fullpath
);
1105 fi
.mdtm
= st
->st_mtime
;
1106 fi
.size
= (longest_int
) st
->st_size
;
1108 (void) AddFileInfo(filp
, &fi
);
1111 cp
= fullpath
+ strlen(fullpath
);
1112 *cp
++ = LOCAL_PATH_DELIM
;
1115 c2
= relpath
+ strlen(relpath
);
1116 *c2
++ = LOCAL_PATH_DELIM
;
1119 memset(&ffd
, 0, sizeof(ffd
));
1121 /* "Open" the directory. */
1122 searchHandle
= FindFirstFile(fullpath
, &ffd
);
1123 if (searchHandle
== INVALID_HANDLE_VALUE
) {
1129 file
= ffd
.cFileName
;
1130 if ((*file
== '.') && ((file
[1] == '\0') || ((file
[1] == '.') && (file
[2] == '\0')))) {
1131 /* It was "." or "..", so skip it. */
1135 (void) strcpy(cp
, file
); /* append name after slash */
1136 (void) strcpy(c2
, file
);
1138 if (Lstat(fullpath
, st
) < 0) {
1139 Error(cip
, kDoPerror
, "could not stat %s.\n", fullpath
);
1143 fi
.relname
= StrDup(relpath
+ (((relpath
[0] == '/') || (relpath
[0] == '\\')) ? 1 : 0));
1145 fi
.lname
= StrDup(fullpath
);
1146 fi
.mdtm
= st
->st_mtime
;
1147 fi
.size
= (longest_int
) st
->st_size
;
1151 if ((ffd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) != 0) {
1152 Traverse(cip
, fullpath
, st
, relpath
, filp
);
1156 (void) AddFileInfo(filp
, &fi
);
1161 memset(&ffd
, 0, sizeof(ffd
));
1163 if (!FindNextFile(searchHandle
, &ffd
)) {
1164 dwErr
= GetLastError();
1165 if (dwErr
!= ERROR_NO_MORE_FILES
) {
1166 FindClose(searchHandle
);
1172 FindClose(searchHandle
);
1178 Traverse(FTPCIPtr cip
, char *fullpath
, struct Stat
*st
, char *relpath
, FileInfoListPtr filp
)
1181 struct dirent
*dirp
;
1188 if (relpath
[0] != '\0') {
1189 fi
.relname
= StrDup(relpath
);
1191 fi
.lname
= StrDup(fullpath
);
1194 fi
.mdtm
= st
->st_mtime
;
1195 fi
.size
= (longest_int
) st
->st_size
;
1197 (void) AddFileInfo(filp
, &fi
);
1200 /* Handle directory entry first. */
1201 cp
= fullpath
+ strlen(fullpath
);
1205 c2
= relpath
+ strlen(relpath
);
1209 if ((dp
= opendir(fullpath
)) == NULL
) {
1212 Error(cip
, kDoPerror
, "could not opendir %s.\n", fullpath
);
1216 while ((dirp
= readdir(dp
)) != NULL
) {
1217 dname
= dirp
->d_name
;
1218 if ((dname
[0] == '.') && ((dname
[1] == '\0') || ((dname
[1] == '.') && (dname
[2] == '\0'))))
1219 continue; /* skip "." and ".." directories. */
1221 (void) strcpy(cp
, dirp
->d_name
); /* append name after slash */
1222 (void) strcpy(c2
, dirp
->d_name
);
1223 if (Lstat(fullpath
, st
) < 0) {
1224 Error(cip
, kDoPerror
, "could not stat %s.\n", fullpath
);
1228 fi
.relname
= StrDup(relpath
+ (((relpath
[0] == '/') || (relpath
[0] == '\\')) ? 1 : 0));
1230 fi
.lname
= StrDup(fullpath
);
1231 fi
.mdtm
= st
->st_mtime
;
1232 fi
.size
= (longest_int
) st
->st_size
;
1237 if (S_ISREG(m
) != 0) {
1240 (void) AddFileInfo(filp
, &fi
);
1241 } else if (S_ISDIR(m
)) {
1242 Traverse(cip
, fullpath
, st
, relpath
, filp
);
1244 } else if (S_ISLNK(m
)) {
1246 fi
.rlinkto
= calloc(128, 1);
1247 if (fi
.rlinkto
!= NULL
) {
1248 if (readlink(fullpath
, fi
.rlinkto
, 127) < 0) {
1251 (void) AddFileInfo(filp
, &fi
);
1254 #endif /* S_ISLNK */
1260 (void) closedir(dp
);
1270 FTPLocalRecursiveFileList2(FTPCIPtr cip
, LineListPtr fileList
, FileInfoListPtr files
, int erelative
)
1272 LinePtr filePtr
, nextFilePtr
;
1273 #if defined(WIN32) || defined(_WINDOWS)
1274 char fullpath
[_MAX_PATH
+ 1];
1275 char relpath
[_MAX_PATH
+ 1];
1284 InitFileInfoList(files
);
1286 for (filePtr
= fileList
->first
;
1288 filePtr
= nextFilePtr
)
1290 nextFilePtr
= filePtr
->next
;
1292 (void) STRNCPY(fullpath
, filePtr
->line
); /* initialize fullpath */
1293 if ((erelative
!= 0) || (strcmp(filePtr
->line
, ".") == 0) || (filePtr
->line
[0] == '\0'))
1294 (void) STRNCPY(relpath
, "");
1295 else if ((cp
= StrRFindLocalPathDelim(filePtr
->line
)) == NULL
)
1296 (void) STRNCPY(relpath
, filePtr
->line
);
1298 (void) STRNCPY(relpath
, cp
+ 1);
1299 if (Lstat(fullpath
, &st
) < 0) {
1300 Error(cip
, kDoPerror
, "could not stat %s.\n", fullpath
);
1304 if (S_ISDIR(st
.st_mode
) == 0) {
1305 fi
.relname
= StrDup(relpath
);
1307 fi
.lname
= StrDup(fullpath
);
1308 fi
.mdtm
= st
.st_mtime
;
1309 fi
.size
= (longest_int
) st
.st_size
;
1313 (void) AddFileInfo(files
, &fi
);
1314 continue; /* wasn't a directory */
1317 /* Paths collected must be relative. */
1318 Traverse(cip
, fullpath
, &st
, relpath
, files
);
1321 } /* FTPLocalRecursiveFileList */
1327 FTPLocalRecursiveFileList(FTPCIPtr cip
, LineListPtr fileList
, FileInfoListPtr files
)
1329 return (FTPLocalRecursiveFileList2(cip
, fileList
, files
, 0));
1330 } /* FTPLocalRecursiveFileList */
1335 FTPRemoteGlob(FTPCIPtr cip
, LineListPtr fileList
, const char *pattern
, int doGlob
)
1338 const char *lsflags
;
1343 return (kErrBadParameter
);
1344 if (strcmp(cip
->magic
, kLibraryMagic
))
1345 return (kErrBadMagic
);
1347 if (fileList
== NULL
)
1348 return (kErrBadParameter
);
1349 InitLineList(fileList
);
1351 if ((pattern
== NULL
) || (pattern
[0] == '\0'))
1352 return (kErrBadParameter
);
1354 /* Note that we do attempt to use glob characters even if the remote
1355 * host isn't UNIX. Most non-UNIX remote FTP servers look for UNIX
1358 if ((doGlob
== 1) && (GLOBCHARSINSTR(pattern
))) {
1359 /* Use NLST, which lists files one per line. */
1362 /* Optimize for "NLST *" case which is same as "NLST". */
1363 if (strcmp(pattern
, "*") == 0) {
1365 } else if (strcmp(pattern
, "**") == 0) {
1366 /* Hack; Lets you try "NLST -a" if you're daring. */
1371 if ((result
= FTPListToMemory2(cip
, pattern
, fileList
, lsflags
, 0, (int *) 0)) < 0) {
1372 if (*lsflags
== '\0')
1374 /* Try again, without "-a" */
1376 if ((result
= FTPListToMemory2(cip
, pattern
, fileList
, lsflags
, 0, (int *) 0)) < 0) {
1380 if (fileList
->first
== NULL
) {
1381 cip
->errNo
= kErrGlobNoMatch
;
1382 return (kErrGlobNoMatch
);
1384 if (fileList
->first
== fileList
->last
) {
1385 #define glberr(a) (ISTRNEQ(cp, a, strlen(a)))
1386 /* If we have only one item in the list, see if it really was
1387 * an error message we would recognize.
1389 cp
= strchr(fileList
->first
->line
, ':');
1391 if (glberr(": No such file or directory")) {
1392 (void) RemoveLine(fileList
, fileList
->first
);
1393 cip
->errNo
= kErrGlobFailed
;
1394 return (kErrGlobFailed
);
1395 } else if (glberr(": No match")) {
1396 cip
->errNo
= kErrGlobNoMatch
;
1397 return (kErrGlobNoMatch
);
1401 RemoteGlobCollapse(pattern
, fileList
);
1402 for (lp
=fileList
->first
; lp
!= NULL
; lp
= lp
->next
)
1403 PrintF(cip
, " Rglob [%s]\n", lp
->line
);
1405 /* Or, if there were no globbing characters in 'pattern', then the
1406 * pattern is really just a filename. So for this case the
1407 * file list is really just a single file.
1409 fileList
->first
= fileList
->last
= NULL
;
1410 (void) AddLine(fileList
, pattern
);
1413 } /* FTPRemoteGlob */
1418 /* This does "tilde-expansion." Examples:
1419 * ~/pub --> /usr/gleason/pub
1420 * ~pdietz/junk --> /usr/pdietz/junk
1423 ExpandTilde(char *pattern
, size_t siz
)
1426 char *cp
, *rest
, *firstent
;
1427 #if defined(WIN32) || defined(_WINDOWS)
1433 if ((pattern
[0] == '~') &&
1434 (isalnum((int) pattern
[1]) || IsLocalPathDelim(pattern
[1]) || (pattern
[1] == '\0'))) {
1435 (void) STRNCPY(pat
, pattern
);
1436 if ((cp
= StrFindLocalPathDelim(pat
)) != NULL
) {
1438 rest
= cp
+ 1; /* Remember stuff after the ~/ part. */
1440 rest
= NULL
; /* Was just a ~ or ~username. */
1442 if (pat
[1] == '\0') {
1443 /* Was just a ~ or ~/rest type. */
1444 GetHomeDir(hdir
, sizeof(hdir
));
1447 #if defined(WIN32) || defined(_WINDOWS)
1450 /* Was just a ~username or ~username/rest type. */
1451 pw
= getpwnam(pat
+ 1);
1453 firstent
= pw
->pw_dir
;
1455 return; /* Bad user -- leave it alone. */
1459 (void) Strncpy(pattern
, firstent
, siz
);
1461 (void) Strncat(pattern
, LOCAL_PATH_DELIM_STR
, siz
);
1462 (void) Strncat(pattern
, rest
, siz
);
1471 #if defined(WIN32) || defined(_WINDOWS)
1474 WinLocalGlob(FTPCIPtr cip
, LineListPtr fileList
, const char *const srcpat
)
1476 char pattern
[_MAX_PATH
];
1477 WIN32_FIND_DATA ffd
;
1478 HANDLE searchHandle
;
1484 STRNCPY(pattern
, srcpat
);
1486 /* Get rid of trailing slashes. */
1487 cp
= pattern
+ strlen(pattern
) - 1;
1488 while ((cp
>= pattern
) && IsLocalPathDelim(*cp
))
1491 memset(&ffd
, 0, sizeof(ffd
));
1493 /* "Open" the directory. */
1494 searchHandle
= FindFirstFile(pattern
, &ffd
);
1495 if (searchHandle
== INVALID_HANDLE_VALUE
) {
1496 dwErr
= GetLastError();
1497 return ((dwErr
== 0) ? 0 : -1);
1500 /* Get rid of basename. */
1501 cp
= StrRFindLocalPathDelim(pattern
);
1508 for (result
= 0;;) {
1509 file
= ffd
.cFileName
;
1510 if ((file
[0] == '.') && ((file
[1] == '\0') || ((file
[1] == '.') && (file
[2] == '\0')))) {
1513 Strncpy(cp
, ffd
.cFileName
, sizeof(pattern
) - (cp
- pattern
));
1514 PrintF(cip
, " Lglob [%s]\n", pattern
);
1515 (void) AddLine(fileList
, pattern
);
1518 if (!FindNextFile(searchHandle
, &ffd
)) {
1519 dwErr
= GetLastError();
1520 if (dwErr
!= ERROR_NO_MORE_FILES
) {
1521 result
= ((dwErr
== 0) ? 0 : -1);
1533 LazyUnixLocalGlob(FTPCIPtr cip
, LineListPtr fileList
, const char *const pattern
)
1540 /* Do it the easy way and have the shell do the dirty
1543 #ifdef HAVE_SNPRINTF
1544 (void) snprintf(cmd
, sizeof(cmd
) - 1, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls",
1546 cmd
[sizeof(cmd
) - 1] = '\0';
1548 (void) sprintf(cmd
, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls",
1552 fp
= (FILE *) popen(cmd
, "r");
1554 Error(cip
, kDoPerror
, "Could not Lglob: [%s]\n", cmd
);
1555 cip
->errNo
= kErrGlobFailed
;
1556 return (kErrGlobFailed
);
1558 sp
= NcSignal(SIGPIPE
, (FTPSigProc
) SIG_IGN
);
1559 while (FGets(gfile
, sizeof(gfile
), (FILE *) fp
) != NULL
) {
1560 PrintF(cip
, " Lglob [%s]\n", gfile
);
1561 (void) AddLine(fileList
, gfile
);
1564 (void) NcSignal(SIGPIPE
, sp
);
1566 } /* LazyUnixLocalGlob */
1574 FTPLocalGlob(FTPCIPtr cip
, LineListPtr fileList
, const char *pattern
, int doGlob
)
1580 return (kErrBadParameter
);
1581 if (strcmp(cip
->magic
, kLibraryMagic
))
1582 return (kErrBadMagic
);
1584 if (fileList
== NULL
)
1585 return (kErrBadParameter
);
1586 InitLineList(fileList
);
1588 if ((pattern
== NULL
) || (pattern
[0] == '\0'))
1589 return (kErrBadParameter
);
1591 (void) STRNCPY(pattern2
, pattern
); /* Don't nuke the original. */
1593 /* Pre-process for ~'s. */
1594 ExpandTilde(pattern2
, sizeof(pattern2
));
1595 InitLineList(fileList
);
1598 if ((doGlob
== 1) && (GLOBCHARSINSTR(pattern2
))) {
1599 #if defined(WIN32) || defined(_WINDOWS)
1600 result
= WinLocalGlob(cip
, fileList
, pattern2
);
1602 result
= LazyUnixLocalGlob(cip
, fileList
, pattern2
);
1605 /* Or, if there were no globbing characters in 'pattern', then
1606 * the pattern is really just a single pathname.
1608 (void) AddLine(fileList
, pattern2
);
1612 } /* FTPLocalGlob */
1618 FTPFtwL2(const FTPCIPtr cip
, char *dir
, char *end
, size_t dirsize
, FTPFtwProc proc
, int maxdepth
)
1625 if (maxdepth
<= 0) {
1626 result
= cip
->errNo
= kErrRecursionLimitReached
;
1630 result
= FTPRemoteGlob(cip
, &fileList
, "**", kGlobYes
);
1631 if (result
!= kNoErr
) {
1632 if (result
== kErrGlobNoMatch
)
1633 result
= kNoErr
; /* empty directory is okay. */
1637 for (filePtr
= fileList
.first
;
1639 filePtr
= filePtr
->next
)
1641 file
= filePtr
->line
;
1643 cip
->errNo
= kErrBadLineList
;
1647 if ((file
[0] == '.') && ((file
[1] == '\0') || ((file
[1] == '.') && (file
[2] == '\0'))))
1648 continue; /* Skip . and .. */
1650 result
= FTPIsDir(cip
, file
);
1653 /* could be just a stat error, so continue */
1655 } else if (result
== 1) {
1657 cp
= Strnpcat(dir
, file
, dirsize
);
1658 result
= (*proc
)(cip
, dir
, kFtwDir
);
1659 if (result
!= kNoErr
)
1662 if ((strchr(dir
, '/') == NULL
) && (strrchr(dir
, '\\') != NULL
))
1668 result
= FTPChdir(cip
, file
);
1669 if (result
== kNoErr
) {
1670 result
= FTPFtwL2(cip
, dir
, cp
, dirsize
, proc
, maxdepth
- 1);
1671 if (result
!= kNoErr
)
1673 if (FTPChdir(cip
, "..") < 0) {
1674 result
= kErrCannotGoToPrevDir
;
1675 cip
->errNo
= kErrCannotGoToPrevDir
;
1685 cp
= Strnpcat(dir
, file
, dirsize
);
1686 result
= (*proc
)(cip
, dir
, kFtwFile
);
1692 DisposeLineListContents(&fileList
);
1700 FTPFtw(const FTPCIPtr cip
, const char *const dir
, FTPFtwProc proc
, int maxdepth
)
1702 int result
, result2
;
1704 char savedcwd
[1024];
1707 result
= FTPIsDir(cip
, dir
);
1711 } else if (result
== 0) {
1712 result
= cip
->errNo
= kErrNotADirectory
;
1716 /* Preserve old working directory. */
1717 (void) FTPGetCWD(cip
, savedcwd
, sizeof(savedcwd
));
1719 result
= FTPChdir(cip
, dir
);
1720 if (result
!= kNoErr
) {
1724 /* Get full working directory we just changed to. */
1725 result
= FTPGetCWD(cip
, curcwd
, sizeof(curcwd
) - 3);
1726 if (result
!= kNoErr
) {
1727 if (FTPChdir(cip
, savedcwd
) != kNoErr
) {
1728 result
= kErrCannotGoToPrevDir
;
1729 cip
->errNo
= kErrCannotGoToPrevDir
;
1734 result2
= (*proc
)(cip
, curcwd
, kFtwDir
);
1735 if (result2
== kNoErr
) {
1736 cp
= curcwd
+ strlen(curcwd
);
1738 if ((strchr(curcwd
, '/') == NULL
) && (strrchr(curcwd
, '\\') != NULL
))
1743 result
= FTPFtwL2(cip
, curcwd
, cp
, sizeof(curcwd
), proc
, maxdepth
- 1);
1747 if (FTPChdir(cip
, savedcwd
) != kNoErr
) {
1748 /* Could not cd back to the original user directory -- bad. */
1749 result
= kErrCannotGoToPrevDir
;
1750 cip
->errNo
= kErrCannotGoToPrevDir
;
1754 if ((result2
!= kNoErr
) && (result
== kNoErr
))