/* glob.c * * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft. * All rights reserved. * */ #include "syshdrs.h" static const char *rwx[9] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx", NULL }; /* We need to use this because using NLST gives us more stuff than * we want back sometimes. For example, say we have: * * /a (directory) * /a/b (directory) * /a/b/b1 * /a/b/b2 * /a/b/b3 * /a/c (directory) * /a/c/c1 * /a/c/c2 * /a/c/c3 * /a/file * * If you did an "echo /a/" in a normal unix shell, you would expect * to get back /a/b /a/c /a/file. But NLST gives back: * * /a/b/b1 * /a/b/b2 * /a/b/b3 * /a/c/c1 * /a/c/c2 * /a/c/c3 * /a/file * * So we use the following routine to convert into the format I expect. */ static void RemoteGlobCollapse(const char *pattern, LineListPtr fileList) { LinePtr lp, nextLine; string patPrefix; string cur, prev; char *endp, *cp, *dp; const char *pp; int wasGlobChar; size_t plen; /* Copy all characters before the first glob-char. */ dp = patPrefix; endp = dp + sizeof(patPrefix) - 1; wasGlobChar = 0; for (cp = (char *) pattern; dp < endp; ) { for (pp=kGlobChars; *pp != '\0'; pp++) { if (*pp == *cp) { wasGlobChar = 1; break; } } if (wasGlobChar) break; *dp++ = *cp++; } *dp = '\0'; plen = (size_t) (dp - patPrefix); *prev = '\0'; for (lp=fileList->first; lp != NULL; lp = nextLine) { nextLine = lp->next; if (strncmp(lp->line, patPrefix, plen) == 0) { (void) STRNCPY(cur, lp->line + plen); cp = strchr(cur, '/'); if (cp == NULL) cp = strchr(cur, '\\'); if (cp != NULL) *cp = '\0'; if ((*prev != '\0') && (STREQ(cur, prev))) { nextLine = RemoveLine(fileList, lp); } else { (void) STRNCPY(prev, cur); /* We are playing with a dynamically * allocated string, but since the * following expression is guaranteed * to be the same or shorter, we won't * overwrite the bounds. */ (void) sprintf(lp->line, "%s%s", patPrefix, cur); } } } } /* RemoteGlobCollapse */ #if 0 /* May need this later. */ static void CheckForLS_d(FTPCIPtr cip) { LineList lines; char *cp; if (cip->hasNLST_d == kCommandAvailabilityUnknown) { if (FTPListToMemory2(cip, ".", &lines, "-d ", 0, (int *) 0) == kNoErr) { if ((lines.first != NULL) && (lines.first == lines.last)) { /* If we have only one item in the list, see if it really was * an error message we would recognize. */ cp = strchr(lines.first->line, ':'); if ((cp != NULL) && STREQ(cp, ": No such file or directory")) { cip->hasNLST_d = kCommandNotAvailable; } else { cip->hasNLST_d = kCommandAvailable; } } else { cip->hasNLST_d = kCommandNotAvailable; } } else { cip->hasNLST_d = kCommandNotAvailable; } DisposeLineListContents(&lines); } } /* CheckForLS_d */ #endif static int LsMonthNameToNum(char *cp) { int mon; /* 0..11 */ switch (*cp++) { case 'A': mon = (*cp == 'u') ? 7 : 3; break; case 'D': mon = 11; break; case 'F': mon = 1; break; default: case 'J': if (*cp++ == 'u') mon = (*cp == 'l') ? 6 : 5; else mon = 0; break; case 'M': mon = (*++cp == 'r') ? 2 : 4; break; case 'N': mon = 10; break; case 'O': mon = 9; break; case 'S': mon = 8; } return (mon); } /* LsMonthNameToNum */ static int UnDosLine( char *const line, const char *const curdir, size_t curdirlen, char *fname, size_t fnamesize, int *ftype, longest_int *fsize, time_t *ftime) { char *cp; int hour, year; char *filestart; char *sizestart; struct tm ftm; /* * 0123456789012345678901234567890123456789012345678901234567890123456789 04-27-99 10:32PM 270158 Game booklet.pdf 03-11-99 10:03PM Get A3d Banner We also try to parse the format from CMD.EXE, which is similar: 03/22/2001 06:23p 62,325 cls.pdf * */ cp = line; if ( isdigit((int) cp[0]) && isdigit((int) cp[1]) && ispunct((int) cp[2]) && isdigit((int) cp[3]) && isdigit((int) cp[4]) && ispunct((int) cp[5]) && isdigit((int) cp[6]) && isdigit((int) cp[7]) ) { (void) memset(&ftm, 0, sizeof(struct tm)); ftm.tm_isdst = -1; cp[2] = '\0'; ftm.tm_mon = atoi(cp + 0); if (ftm.tm_mon > 0) ftm.tm_mon -= 1; cp[5] = '\0'; ftm.tm_mday = atoi(cp + 3); if ((isdigit((int) cp[8])) && (isdigit((int) cp[9]))) { /* Four-digit year */ cp[10] = '\0'; year = atoi(cp + 6); if (year > 1900) year -= 1900; ftm.tm_year = year; /* years since 1900 */ cp += 11; } else { /* Two-digit year */ cp[8] = '\0'; year = atoi(cp + 6); if (year < 98) year += 100; ftm.tm_year = year; /* years since 1900 */ cp += 9; } for (;;) { if (*cp == '\0') return (-1); if (isdigit(*cp)) break; cp++; } cp[2] = '\0'; hour = atoi(cp); if (((cp[5] == 'P') || (cp[5] == 'p')) && (hour < 12)) hour += 12; else if (((cp[5] == 'A') || (cp[5] == 'a')) && (hour == 12)) hour -= 12; ftm.tm_hour = hour; cp[5] = '\0'; ftm.tm_min = atoi(cp + 3); *ftime = mktime(&ftm); if (*ftype == (time_t) -1) return (-1); cp += 6; *ftype = '-'; for (;;) { if (*cp == '\0') return (-1); if ((*cp == '<') && (cp[1] == 'D')) { /* found */ *ftype = 'd'; cp += 5; break; /* size field will end up being empty string */ } else if ((*cp == '<') && (cp[1] == 'J')) { /* found * * Will we ever really see this? * IIS from Win2000sp1 sends * for FTP, but CMD.EXE prints * . */ *ftype = 'd'; cp += 10; break; } else if (isdigit(*cp)) { break; } else { cp++; } } sizestart = cp; for (;;) { if (*cp == '\0') return (-1); #ifdef HAVE_MEMMOVE if (*cp == ',') { /* Yuck -- US Locale dependency */ memmove(cp, cp + 1, strlen(cp + 1) + 1); } #endif if (!isdigit(*cp)) { *cp++ = '\0'; break; } cp++; } if (fsize != NULL) { #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG) if (*ftype == 'd') *fsize = 0; else (void) sscanf(sizestart, SCANF_LONG_LONG, fsize); #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ) if (*ftype == 'd') *fsize = 0; else *fsize = (longest_int) strtoq(sizestart, NULL, 0); #else *fsize = (longest_int) 0; if (*ftype != 'd') { long fsize2 = 0L; (void) sscanf(sizestart, "%ld", &fsize2); *fsize = (longest_int) fsize2; } #endif } for (;;) { if (*cp == '\0') return (-1); if (!isspace(*cp)) { break; } cp++; } filestart = cp; if (curdirlen == 0) { (void) Strncpy(fname, filestart, fnamesize); } else { (void) Strncpy(fname, curdir, fnamesize); (void) Strncat(fname, filestart, fnamesize); } return (0); } return (-1); } /* UnDosLine */ static int UnLslRLine( char *const line, const char *const curdir, size_t curdirlen, char *fname, size_t fnamesize, char *linkto, size_t linktosize, int *ftype, longest_int *fsize, time_t *ftime, time_t now, int thisyear, int *plugend) { char *cp; int mon = 0, dd = 0, hr = 0, min = 0, year = 0; char *monstart, *ddstart, *hrstart, *minstart, *yearstart; char *linktostart, *filestart = NULL; char *sizestart; char *pe; struct tm ftm; /* * Look for the digit just before the space * before the month name. * -rw-rw---- 1 gleason sysdev 33404 Mar 24 01:29 RCmd.o -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README -rw-rw-r-- 1 gleason sysdevzz 1829 Jul 7 1996 README * *------------------------------^ * 0123456789012345 *------plugend--------^ * 9876543210 * */ for (cp = line; *cp != '\0'; cp++) { if ( (isdigit((int) *cp)) && (isspace((int) cp[1])) && (isupper((int) cp[2])) && (islower((int) cp[3])) /* && (islower((int) cp[4])) */ && (isspace((int) cp[5])) && ( ((isdigit((int) cp[6])) && (isdigit((int) cp[7]))) || ((isdigit((int) cp[6])) && (isspace((int) cp[7]))) || ((isspace((int) cp[6])) && (isdigit((int) cp[7]))) ) && (isspace((int) cp[8])) ) { monstart = cp + 2; ddstart = cp + 6; if ( ((isspace((int) cp[9])) || (isdigit((int) cp[9]))) && (isdigit((int) cp[10])) && (isdigit((int) cp[11])) && (isdigit((int) cp[12])) && ((isdigit((int) cp[13])) || (isspace((int) cp[13]))) ) { /* "Mon DD YYYY" form */ yearstart = cp + 9; if (isspace((int) *yearstart)) yearstart++; hrstart = NULL; minstart = NULL; filestart = cp + 15; cp[1] = '\0'; /* end size */ cp[5] = '\0'; /* end mon */ cp[8] = '\0'; /* end dd */ cp[14] = '\0'; /* end year */ mon = LsMonthNameToNum(monstart); dd = atoi(ddstart); hr = 23; min = 59; year = atoi(yearstart); pe = cp; while (isdigit((int) *pe)) pe--; while (isspace((int) *pe)) pe--; *plugend = (int) (pe - line) + 1; break; } else if ( /* * Windows NT does not 0 pad. (isdigit((int) cp[9])) && */ (isdigit((int) cp[10])) && (cp[11] == ':') && (isdigit((int) cp[12])) && (isdigit((int) cp[13])) ) { /* "Mon DD HH:MM" form */ yearstart = NULL; hrstart = cp + 9; minstart = cp + 12; filestart = cp + 15; cp[1] = '\0'; /* end size */ cp[5] = '\0'; /* end mon */ cp[8] = '\0'; /* end dd */ cp[11] = '\0'; /* end hr */ cp[14] = '\0'; /* end min */ mon = LsMonthNameToNum(monstart); dd = atoi(ddstart); hr = atoi(hrstart); min = atoi(minstart); year = 0; pe = cp; while (isdigit((int) *pe)) pe--; while (isspace((int) *pe)) pe--; *plugend = (int) (pe - line) + 1; break; } } } if (*cp == '\0') return (-1); linktostart = strstr(filestart, " -> "); if (linktostart != NULL) { *linktostart = '\0'; linktostart += 4; (void) Strncpy(linkto, linktostart, linktosize); } else { *linkto = '\0'; } if (curdirlen == 0) { (void) Strncpy(fname, filestart, fnamesize); } else { (void) Strncpy(fname, curdir, fnamesize); (void) Strncat(fname, filestart, fnamesize); } if (ftime != NULL) { (void) memset(&ftm, 0, sizeof(struct tm)); ftm.tm_mon = mon; ftm.tm_mday = dd; ftm.tm_hour = hr; ftm.tm_min = min; ftm.tm_isdst = -1; if (year == 0) { /* We guess the year, based on what the * current year is. We know the file * on the remote server is either less * than six months old or less than * one hour into the future. */ ftm.tm_year = thisyear - 1900; *ftime = mktime(&ftm); if (*ftime == (time_t) -1) { /* panic */ } else if (*ftime > (now + (15552000L + 86400L))) { --ftm.tm_year; *ftime = mktime(&ftm); } else if (*ftime < (now - (15552000L + 86400L))) { ++ftm.tm_year; *ftime = mktime(&ftm); } } else { ftm.tm_year = year - 1900; *ftime = mktime(&ftm); } } if (fsize != NULL) { while ((cp > line) && (isdigit((int) *cp))) --cp; sizestart = cp + 1; #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG) (void) sscanf(sizestart, SCANF_LONG_LONG, fsize); #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ) *fsize = (longest_int) strtoq(sizestart, NULL, 0); #else { long fsize2 = 0L; (void) sscanf(sizestart, "%ld", &fsize2); *fsize = (longest_int) fsize2; } #endif } switch (line[0]) { case 'd': case 'l': *ftype = (int) line[0]; break; case 'b': case 'c': case 's': *ftype = (int) line[0]; return (-1); default: *ftype = '-'; } return (0); } /* UnLslRLine */ int UnLslR(FileInfoListPtr filp, LineListPtr llp, int serverType) { char curdir[256]; char line[256]; int hadblankline = 0; int len; size_t curdirlen = 0; char fname[256]; char linkto[256]; char *cp; longest_int fsize; int ftype; time_t ftime, now; int thisyear; struct tm *nowtm; int rc; LinePtr lp; FileInfo fi; int linesread = 0; int linesconverted = 0; size_t maxFileLen = 0; size_t maxPlugLen = 0; size_t fileLen; int plugend; (void) time(&now); nowtm = localtime(&now); if (nowtm == NULL) thisyear = 1970; /* should never happen */ else thisyear = nowtm->tm_year + 1900; curdir[0] = '\0'; InitFileInfoList(filp); for (lp = llp->first; lp != NULL; lp = lp->next) { len = (int) strlen(STRNCPY(line, lp->line)); if ((line[0] == 't') && (strncmp(line, "total", 5) == 0)) { /* total XX line? */ if (line[len - 1] != ':') { hadblankline = 0; continue; } /* else it was a subdir named total */ } else { for (cp = line; ; cp++) { if ((*cp == '\0') || (!isspace((int) *cp))) break; } if (*cp == '\0') { /* Entire line was blank. */ /* separator line between dirs */ hadblankline = 1; continue; } } if ((hadblankline != 0) && (line[len - 1] == ':')) { /* newdir */ hadblankline = 0; if ((line[0] == '.') && (line[1] == '/')) { line[len - 1] = '/'; (void) memcpy(curdir, line + 2, (size_t) len + 1 - 2); curdirlen = (size_t) (len - 2); } else if ((line[0] == '.') && (line[1] == '\\')) { line[len - 1] = '\\'; (void) memcpy(curdir, line + 2, (size_t) len + 1 - 2); curdirlen = (size_t) (len - 2); } else { line[len - 1] = '/'; (void) memcpy(curdir, line, (size_t) len + 1); curdirlen = (size_t) len; } continue; } linesread++; rc = UnLslRLine(line, curdir, curdirlen, fname, sizeof(fname), linkto, sizeof(linkto), &ftype, &fsize, &ftime, now, thisyear, &plugend); if ((rc < 0) && (serverType == kServerTypeMicrosoftFTP)) { rc = UnDosLine(line, curdir, curdirlen, fname, sizeof(fname), &ftype, &fsize, &ftime); if (rc == 0) { *linkto = '\0'; plugend = 0; } } if (rc == 0) { linesconverted++; fileLen = strlen(fname); if (fileLen > maxFileLen) maxFileLen = fileLen; fi.relnameLen = fileLen; fi.relname = StrDup(fname); fi.rname = NULL; fi.lname = NULL; fi.rlinkto = (linkto[0] == '\0') ? NULL : StrDup(linkto); fi.mdtm = ftime; fi.size = (longest_int) fsize; fi.type = ftype; if (plugend > 0) { fi.plug = (char *) malloc((size_t) plugend + 1); if (fi.plug != NULL) { (void) memcpy(fi.plug, line, (size_t) plugend); fi.plug[plugend] = '\0'; if ((size_t) plugend > maxPlugLen) maxPlugLen = (size_t) plugend; } } else { fi.plug = (char *) malloc(32); if (fi.plug != NULL) { strcpy(fi.plug, "---------- 1 ftpuser ftpusers"); fi.plug[0] = (char) ftype; if (30 > maxPlugLen) maxPlugLen = (size_t) 30; } } (void) AddFileInfo(filp, &fi); } hadblankline = 0; } filp->maxFileLen = maxFileLen; filp->maxPlugLen = maxPlugLen; if (linesread == 0) return (0); return ((linesconverted > 0) ? linesconverted : (-1)); } /* UnLslR */ int UnMlsT(const char *const line0, const MLstItemPtr mlip) { char *cp, *val, *fact; int ec; size_t len; char line[1024]; memset(mlip, 0, sizeof(MLstItem)); mlip->mode = -1; mlip->fsize = kSizeUnknown; mlip->ftype = '-'; mlip->ftime = kModTimeUnknown; len = strlen(line0); if (len > (sizeof(line) - 1)) return (-1); /* Line too long, sorry. */ /* This should be re-coded so does not need to make a * copy of the buffer; it could be done in place. */ memcpy(line, line0, len + 1); /* Skip leading whitespace. */ for (cp = line; *cp != '\0'; cp++) { if (! isspace(*cp)) break; } while (*cp != '\0') { for (fact = cp; ; cp++) { if ((*cp == '\0') || (*cp == ' ')) { /* protocol violation */ return (-1); } if (*cp == '=') { /* End of fact name. */ *cp++ = '\0'; break; } } for (val = cp; ; cp++) { if (*cp == '\0') { /* protocol violation */ return (-1); } if (*cp == ' ') { ec = ' '; *cp++ = '\0'; break; } else if (*cp == ';') { if (cp[1] == ' ') { ec = ' '; *cp++ = '\0'; *cp++ = '\0'; } else { ec = ';'; *cp++ = '\0'; } break; } } if (ISTRNEQ(fact, "OS.", 3)) fact += 3; if (ISTREQ(fact, "type")) { if (ISTREQ(val, "file")) { mlip->ftype = '-'; } else if (ISTREQ(val, "dir")) { mlip->ftype = 'd'; } else if (ISTREQ(val, "cdir")) { /* not supported: current directory */ return (-2); } else if (ISTREQ(val, "pdir")) { /* not supported: parent directory */ return (-2); } else { /* ? */ return (-1); } } else if (ISTREQ(fact, "UNIX.mode")) { if (val[0] == '0') sscanf(val, "%o", &mlip->mode); else sscanf(val, "%i", &mlip->mode); if (mlip->mode != (-1)) mlip->mode &= 00777; } else if (ISTREQ(fact, "perm")) { STRNCPY(mlip->perm, val); } else if (ISTREQ(fact, "size")) { #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG) (void) sscanf(val, SCANF_LONG_LONG, &mlip->fsize); #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ) mlip->fsize = (longest_int) strtoq(val, NULL, 0); #else { long fsize2 = 0L; (void) sscanf(val, "%ld", &fsize2); mlip->fsize = (longest_int) fsize2; } #endif } else if (ISTREQ(fact, "modify")) { mlip->ftime = UnMDTMDate(val); } else if (ISTREQ(fact, "UNIX.owner")) { STRNCPY(mlip->owner, val); } else if (ISTREQ(fact, "UNIX.group")) { STRNCPY(mlip->group, val); } else if (ISTREQ(fact, "UNIX.uid")) { mlip->uid = atoi(val); } else if (ISTREQ(fact, "UNIX.gid")) { mlip->gid = atoi(val); } else if (ISTREQ(fact, "perm")) { STRNCPY(mlip->perm, val); } /* End of facts? */ if (ec == ' ') break; } len = strlen(cp); if (len > (sizeof(mlip->fname) - 1)) { /* Filename too long */ return (-1); } memcpy(mlip->fname, cp, len); /* also set linkto here if used */ return (0); } /* UnMlsT */ int UnMlsD(FileInfoListPtr filp, LineListPtr llp) { MLstItem mli; char plug[64]; char og[32]; int rc; LinePtr lp; FileInfo fi; int linesread = 0; int linesconverted = 0; int linesignored = 0; size_t maxFileLen = 0; size_t maxPlugLen = 0; size_t fileLen, plugLen; int m1, m2, m3; const char *cm1, *cm2, *cm3; InitFileInfoList(filp); for (lp = llp->first; lp != NULL; lp = lp->next) { linesread++; rc = UnMlsT(lp->line, &mli); if (rc == 0) { linesconverted++; fileLen = strlen(mli.fname); if (fileLen > maxFileLen) maxFileLen = fileLen; fi.relnameLen = fileLen; fi.relname = StrDup(mli.fname); fi.rname = NULL; fi.lname = NULL; fi.rlinkto = (mli.linkto[0] == '\0') ? NULL : StrDup(mli.linkto); fi.mdtm = mli.ftime; fi.size = (longest_int) mli.fsize; fi.type = mli.ftype; plug[0] = (char) mli.ftype; plug[1] = '\0'; m1 = 0; m2 = 0; m3 = -1; if (mli.mode != (-1)) { m1 = (mli.mode & 00700) >> 6; m2 = (mli.mode & 00070) >> 3; m3 = (mli.mode & 00007); } if (mli.perm[0] != '\0') { m3 = 0; if (fi.type == 'd') { if (strchr(mli.perm, 'e') != NULL) { /* execute -> execute */ m3 |= 00001; } if (strchr(mli.perm, 'c') != NULL) { /* create -> write */ m3 |= 00002; } if (strchr(mli.perm, 'l') != NULL) { /* list -> read */ m3 |= 00004; } } else { if (strchr(mli.perm, 'w') != NULL) { /* write -> write */ m3 |= 00002; } if (strchr(mli.perm, 'r') != NULL) { /* read -> read */ m3 |= 00004; } } } if (m3 != (-1)) { cm1 = rwx[m1]; cm2 = rwx[m2]; cm3 = rwx[m3]; sprintf(plug + 1, "%s%s%s", cm1, cm2, cm3); } if (mli.owner[0] != '\0') { if (mli.group[0] != '\0') { #ifdef HAVE_SNPRINTF snprintf(og, sizeof(og) - 1, #else sprintf(og, #endif /* HAVE_SNPRINTF */ " %-8.8s %s", mli.owner, mli.group ); STRNCAT(plug, og); } else { STRNCAT(plug, " "); STRNCAT(plug, mli.owner); } } fi.plug = StrDup(plug); if (fi.plug != NULL) { plugLen = strlen(plug); if (plugLen > maxPlugLen) maxPlugLen = plugLen; } (void) AddFileInfo(filp, &fi); } else if (rc == (-2)) { linesignored++; } } filp->maxFileLen = maxFileLen; filp->maxPlugLen = maxPlugLen; if (linesread == 0) return (0); linesconverted += linesignored; return ((linesconverted > 0) ? linesconverted : (-1)); } /* UnMlsD */ #if 0 static void print1(FileInfoListPtr list) { FileInfoPtr fip; int i; for (i = 1, fip = list->first; fip != NULL; fip = fip->next, i++) printf("%d: %s\n", i, fip->relname == NULL ? "NULL" : fip->relname); } static void print2(FileInfoListPtr list) { FileInfoPtr fip; int i, n; n = list->nFileInfos; for (i=0; ivec[i]; printf("%d: %s\n", i + 1, fip->relname == NULL ? "NULL" : fip->relname); } } static void SortRecursiveFileList(FileInfoListPtr files) { VectorizeFileInfoList(files); SortFileInfoList(files, 'b', '?'); UnvectorizeFileInfoList(files); } /* SortRecursiveFileList */ #endif int FTPRemoteRecursiveFileList1(FTPCIPtr cip, char *const rdir, FileInfoListPtr files) { LineList dirContents; FileInfoList fil; char cwd[512]; int result; if ((result = FTPGetCWD(cip, cwd, sizeof(cwd))) < 0) return (result); InitFileInfoList(files); if (rdir == NULL) return (-1); if (FTPChdir(cip, rdir) < 0) { /* Probably not a directory. * Just add it as a plain file * to the list. */ (void) ConcatFileToFileInfoList(files, rdir); return (kNoErr); } /* Paths collected must be relative. */ if ((result = FTPListToMemory2(cip, "", &dirContents, "-lRa", 1, (int *) 0)) < 0) { if ((result = FTPChdir(cip, cwd)) < 0) { return (result); } } (void) UnLslR(&fil, &dirContents, cip->serverType); DisposeLineListContents(&dirContents); /* Could sort it to breadth-first here. */ /* (void) SortRecursiveFileList(&fil); */ (void) ComputeRNames(&fil, rdir, 1, 1); (void) ConcatFileInfoList(files, &fil); DisposeFileInfoListContents(&fil); if ((result = FTPChdir(cip, cwd)) < 0) { return (result); } return (kNoErr); } /* FTPRemoteRecursiveFileList1 */ int FTPRemoteRecursiveFileList(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files) { LinePtr filePtr, nextFilePtr; LineList dirContents; FileInfoList fil; char cwd[512]; int result; char *rdir; if ((result = FTPGetCWD(cip, cwd, sizeof(cwd))) < 0) return (result); InitFileInfoList(files); for (filePtr = fileList->first; filePtr != NULL; filePtr = nextFilePtr) { nextFilePtr = filePtr->next; rdir = filePtr->line; if (rdir == NULL) continue; if (FTPChdir(cip, rdir) < 0) { /* Probably not a directory. * Just add it as a plain file * to the list. */ (void) ConcatFileToFileInfoList(files, rdir); continue; } /* Paths collected must be relative. */ if ((result = FTPListToMemory2(cip, "", &dirContents, "-lRa", 1, (int *) 0)) < 0) { goto goback; } (void) UnLslR(&fil, &dirContents, cip->serverType); DisposeLineListContents(&dirContents); (void) ComputeRNames(&fil, rdir, 1, 1); (void) ConcatFileInfoList(files, &fil); DisposeFileInfoListContents(&fil); goback: if ((result = FTPChdir(cip, cwd)) < 0) { return (result); } } return (kNoErr); } /* FTPRemoteRecursiveFileList */ #if defined(WIN32) || defined(_WINDOWS) static void Traverse(FTPCIPtr cip, char *fullpath, struct Stat *st, char *relpath, FileInfoListPtr filp) { WIN32_FIND_DATA ffd; HANDLE searchHandle; DWORD dwErr; char *cp, *c2; const char *file; FileInfo fi; /* Handle directory entry first. */ if (relpath[0] != '\0') { fi.relname = StrDup(relpath); fi.rname = NULL; fi.lname = StrDup(fullpath); fi.rlinkto = NULL; fi.plug = NULL; fi.mdtm = st->st_mtime; fi.size = (longest_int) st->st_size; fi.type = 'd'; (void) AddFileInfo(filp, &fi); } cp = fullpath + strlen(fullpath); *cp++ = LOCAL_PATH_DELIM; strcpy(cp, "*.*"); c2 = relpath + strlen(relpath); *c2++ = LOCAL_PATH_DELIM; *c2 = '\0'; memset(&ffd, 0, sizeof(ffd)); /* "Open" the directory. */ searchHandle = FindFirstFile(fullpath, &ffd); if (searchHandle == INVALID_HANDLE_VALUE) { return; } for (;;) { file = ffd.cFileName; if ((*file == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) { /* It was "." or "..", so skip it. */ goto next; } (void) strcpy(cp, file); /* append name after slash */ (void) strcpy(c2, file); if (Lstat(fullpath, st) < 0) { Error(cip, kDoPerror, "could not stat %s.\n", fullpath); goto next; } fi.relname = StrDup(relpath + (((relpath[0] == '/') || (relpath[0] == '\\')) ? 1 : 0)); fi.rname = NULL; fi.lname = StrDup(fullpath); fi.mdtm = st->st_mtime; fi.size = (longest_int) st->st_size; fi.rlinkto = NULL; fi.plug = NULL; if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { Traverse(cip, fullpath, st, relpath, filp); } else { /* file */ fi.type = '-'; (void) AddFileInfo(filp, &fi); } next: #if _DEBUG memset(&ffd, 0, sizeof(ffd)); #endif if (!FindNextFile(searchHandle, &ffd)) { dwErr = GetLastError(); if (dwErr != ERROR_NO_MORE_FILES) { FindClose(searchHandle); return; } break; } } FindClose(searchHandle); } // Traverse #else static void Traverse(FTPCIPtr cip, char *fullpath, struct Stat *st, char *relpath, FileInfoListPtr filp) { char *dname; struct dirent *dirp; mode_t m; DIR *dp; char *cp; char *c2; FileInfo fi; if (relpath[0] != '\0') { fi.relname = StrDup(relpath); fi.rname = NULL; fi.lname = StrDup(fullpath); fi.rlinkto = NULL; fi.plug = NULL; fi.mdtm = st->st_mtime; fi.size = (longest_int) st->st_size; fi.type = 'd'; (void) AddFileInfo(filp, &fi); } /* Handle directory entry first. */ cp = fullpath + strlen(fullpath); *cp++ = '/'; *cp = '\0'; c2 = relpath + strlen(relpath); *c2++ = '/'; *c2 = '\0'; if ((dp = opendir(fullpath)) == NULL) { cp[-1] = '\0'; c2[-1] = '\0'; Error(cip, kDoPerror, "could not opendir %s.\n", fullpath); return; } while ((dirp = readdir(dp)) != NULL) { dname = dirp->d_name; if ((dname[0] == '.') && ((dname[1] == '\0') || ((dname[1] == '.') && (dname[2] == '\0')))) continue; /* skip "." and ".." directories. */ (void) strcpy(cp, dirp->d_name); /* append name after slash */ (void) strcpy(c2, dirp->d_name); if (Lstat(fullpath, st) < 0) { Error(cip, kDoPerror, "could not stat %s.\n", fullpath); continue; } fi.relname = StrDup(relpath + (((relpath[0] == '/') || (relpath[0] == '\\')) ? 1 : 0)); fi.rname = NULL; fi.lname = StrDup(fullpath); fi.mdtm = st->st_mtime; fi.size = (longest_int) st->st_size; fi.rlinkto = NULL; fi.plug = NULL; m = st->st_mode; if (S_ISREG(m) != 0) { /* file */ fi.type = '-'; (void) AddFileInfo(filp, &fi); } else if (S_ISDIR(m)) { Traverse(cip, fullpath, st, relpath, filp); #ifdef S_ISLNK } else if (S_ISLNK(m)) { fi.type = 'l'; fi.rlinkto = calloc(128, 1); if (fi.rlinkto != NULL) { if (readlink(fullpath, fi.rlinkto, 127) < 0) { free(fi.rlinkto); } else { (void) AddFileInfo(filp, &fi); } } #endif /* S_ISLNK */ } } cp[-1] = '\0'; c2[-1] = '\0'; (void) closedir(dp); } /* Traverse */ #endif int FTPLocalRecursiveFileList2(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files, int erelative) { LinePtr filePtr, nextFilePtr; #if defined(WIN32) || defined(_WINDOWS) char fullpath[_MAX_PATH + 1]; char relpath[_MAX_PATH + 1]; #else char fullpath[512]; char relpath[512]; #endif struct Stat st; FileInfo fi; char *cp; InitFileInfoList(files); for (filePtr = fileList->first; filePtr != NULL; filePtr = nextFilePtr) { nextFilePtr = filePtr->next; (void) STRNCPY(fullpath, filePtr->line); /* initialize fullpath */ if ((erelative != 0) || (strcmp(filePtr->line, ".") == 0) || (filePtr->line[0] == '\0')) (void) STRNCPY(relpath, ""); else if ((cp = StrRFindLocalPathDelim(filePtr->line)) == NULL) (void) STRNCPY(relpath, filePtr->line); else (void) STRNCPY(relpath, cp + 1); if (Lstat(fullpath, &st) < 0) { Error(cip, kDoPerror, "could not stat %s.\n", fullpath); continue; } if (S_ISDIR(st.st_mode) == 0) { fi.relname = StrDup(relpath); fi.rname = NULL; fi.lname = StrDup(fullpath); fi.mdtm = st.st_mtime; fi.size = (longest_int) st.st_size; fi.rlinkto = NULL; fi.plug = NULL; fi.type = '-'; (void) AddFileInfo(files, &fi); continue; /* wasn't a directory */ } /* Paths collected must be relative. */ Traverse(cip, fullpath, &st, relpath, files); } return (kNoErr); } /* FTPLocalRecursiveFileList */ int FTPLocalRecursiveFileList(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files) { return (FTPLocalRecursiveFileList2(cip, fileList, files, 0)); } /* FTPLocalRecursiveFileList */ int FTPRemoteGlob(FTPCIPtr cip, LineListPtr fileList, const char *pattern, int doGlob) { char *cp; const char *lsflags; LinePtr lp; int result; if (cip == NULL) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); if (fileList == NULL) return (kErrBadParameter); InitLineList(fileList); if ((pattern == NULL) || (pattern[0] == '\0')) return (kErrBadParameter); /* Note that we do attempt to use glob characters even if the remote * host isn't UNIX. Most non-UNIX remote FTP servers look for UNIX * style wildcards. */ if ((doGlob == 1) && (GLOBCHARSINSTR(pattern))) { /* Use NLST, which lists files one per line. */ lsflags = ""; /* Optimize for "NLST *" case which is same as "NLST". */ if (strcmp(pattern, "*") == 0) { pattern = ""; } else if (strcmp(pattern, "**") == 0) { /* Hack; Lets you try "NLST -a" if you're daring. */ pattern = ""; lsflags = "-a"; } if ((result = FTPListToMemory2(cip, pattern, fileList, lsflags, 0, (int *) 0)) < 0) { if (*lsflags == '\0') return (result); /* Try again, without "-a" */ lsflags = ""; if ((result = FTPListToMemory2(cip, pattern, fileList, lsflags, 0, (int *) 0)) < 0) { return (result); } } if (fileList->first == NULL) { cip->errNo = kErrGlobNoMatch; return (kErrGlobNoMatch); } if (fileList->first == fileList->last) { #define glberr(a) (ISTRNEQ(cp, a, strlen(a))) /* If we have only one item in the list, see if it really was * an error message we would recognize. */ cp = strchr(fileList->first->line, ':'); if (cp != NULL) { if (glberr(": No such file or directory")) { (void) RemoveLine(fileList, fileList->first); cip->errNo = kErrGlobFailed; return (kErrGlobFailed); } else if (glberr(": No match")) { cip->errNo = kErrGlobNoMatch; return (kErrGlobNoMatch); } } } RemoteGlobCollapse(pattern, fileList); for (lp=fileList->first; lp != NULL; lp = lp->next) PrintF(cip, " Rglob [%s]\n", lp->line); } else { /* Or, if there were no globbing characters in 'pattern', then the * pattern is really just a filename. So for this case the * file list is really just a single file. */ fileList->first = fileList->last = NULL; (void) AddLine(fileList, pattern); } return (kNoErr); } /* FTPRemoteGlob */ /* This does "tilde-expansion." Examples: * ~/pub --> /usr/gleason/pub * ~pdietz/junk --> /usr/pdietz/junk */ static void ExpandTilde(char *pattern, size_t siz) { string pat; char *cp, *rest, *firstent; #if defined(WIN32) || defined(_WINDOWS) #else struct passwd *pw; #endif string hdir; if ((pattern[0] == '~') && (isalnum((int) pattern[1]) || IsLocalPathDelim(pattern[1]) || (pattern[1] == '\0'))) { (void) STRNCPY(pat, pattern); if ((cp = StrFindLocalPathDelim(pat)) != NULL) { *cp = 0; rest = cp + 1; /* Remember stuff after the ~/ part. */ } else { rest = NULL; /* Was just a ~ or ~username. */ } if (pat[1] == '\0') { /* Was just a ~ or ~/rest type. */ GetHomeDir(hdir, sizeof(hdir)); firstent = hdir; } else { #if defined(WIN32) || defined(_WINDOWS) return; #else /* Was just a ~username or ~username/rest type. */ pw = getpwnam(pat + 1); if (pw != NULL) firstent = pw->pw_dir; else return; /* Bad user -- leave it alone. */ #endif } (void) Strncpy(pattern, firstent, siz); if (rest != NULL) { (void) Strncat(pattern, LOCAL_PATH_DELIM_STR, siz); (void) Strncat(pattern, rest, siz); } } } /* ExpandTilde */ #if defined(WIN32) || defined(_WINDOWS) static int WinLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *const srcpat) { char pattern[_MAX_PATH]; WIN32_FIND_DATA ffd; HANDLE searchHandle; DWORD dwErr; char *cp; const char *file; int result; STRNCPY(pattern, srcpat); /* Get rid of trailing slashes. */ cp = pattern + strlen(pattern) - 1; while ((cp >= pattern) && IsLocalPathDelim(*cp)) *cp-- = '\0'; memset(&ffd, 0, sizeof(ffd)); /* "Open" the directory. */ searchHandle = FindFirstFile(pattern, &ffd); if (searchHandle == INVALID_HANDLE_VALUE) { dwErr = GetLastError(); return ((dwErr == 0) ? 0 : -1); } /* Get rid of basename. */ cp = StrRFindLocalPathDelim(pattern); if (cp == NULL) cp = pattern; else cp++; *cp = '\0'; for (result = 0;;) { file = ffd.cFileName; if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) { /* skip */ } else { Strncpy(cp, ffd.cFileName, sizeof(pattern) - (cp - pattern)); PrintF(cip, " Lglob [%s]\n", pattern); (void) AddLine(fileList, pattern); } if (!FindNextFile(searchHandle, &ffd)) { dwErr = GetLastError(); if (dwErr != ERROR_NO_MORE_FILES) { result = ((dwErr == 0) ? 0 : -1); } break; } } return (result); } // WinLocalGlob #else static int LazyUnixLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *const pattern) { string cmd; longstring gfile; FILE *fp; FTPSigProc sp; /* Do it the easy way and have the shell do the dirty * work for us. */ #ifdef HAVE_SNPRINTF (void) snprintf(cmd, sizeof(cmd) - 1, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls", "-d", pattern); cmd[sizeof(cmd) - 1] = '\0'; #else (void) sprintf(cmd, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls", "-d", pattern); #endif fp = (FILE *) popen(cmd, "r"); if (fp == NULL) { Error(cip, kDoPerror, "Could not Lglob: [%s]\n", cmd); cip->errNo = kErrGlobFailed; return (kErrGlobFailed); } sp = NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN); while (FGets(gfile, sizeof(gfile), (FILE *) fp) != NULL) { PrintF(cip, " Lglob [%s]\n", gfile); (void) AddLine(fileList, gfile); } (void) pclose(fp); (void) NcSignal(SIGPIPE, sp); return (kNoErr); } /* LazyUnixLocalGlob */ #endif int FTPLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *pattern, int doGlob) { string pattern2; int result; if (cip == NULL) return (kErrBadParameter); if (strcmp(cip->magic, kLibraryMagic)) return (kErrBadMagic); if (fileList == NULL) return (kErrBadParameter); InitLineList(fileList); if ((pattern == NULL) || (pattern[0] == '\0')) return (kErrBadParameter); (void) STRNCPY(pattern2, pattern); /* Don't nuke the original. */ /* Pre-process for ~'s. */ ExpandTilde(pattern2, sizeof(pattern2)); InitLineList(fileList); result = kNoErr; if ((doGlob == 1) && (GLOBCHARSINSTR(pattern2))) { #if defined(WIN32) || defined(_WINDOWS) result = WinLocalGlob(cip, fileList, pattern2); #else result = LazyUnixLocalGlob(cip, fileList, pattern2); #endif } else { /* Or, if there were no globbing characters in 'pattern', then * the pattern is really just a single pathname. */ (void) AddLine(fileList, pattern2); } return (result); } /* FTPLocalGlob */ static int FTPFtwL2(const FTPCIPtr cip, char *dir, char *end, size_t dirsize, FTPFtwProc proc, int maxdepth) { LineList fileList; LinePtr filePtr; char *file, *cp; int result; if (maxdepth <= 0) { result = cip->errNo = kErrRecursionLimitReached; return (result); } result = FTPRemoteGlob(cip, &fileList, "**", kGlobYes); if (result != kNoErr) { if (result == kErrGlobNoMatch) result = kNoErr; /* empty directory is okay. */ return (result); } for (filePtr = fileList.first; filePtr != NULL; filePtr = filePtr->next) { file = filePtr->line; if (file == NULL) { cip->errNo = kErrBadLineList; break; } if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) continue; /* Skip . and .. */ result = FTPIsDir(cip, file); if (result < 0) { /* error */ /* could be just a stat error, so continue */ continue; } else if (result == 1) { /* directory */ cp = Strnpcat(dir, file, dirsize); result = (*proc)(cip, dir, kFtwDir); if (result != kNoErr) break; if ((strchr(dir, '/') == NULL) && (strrchr(dir, '\\') != NULL)) *cp++ = '\\'; else *cp++ = '/'; *cp = '\0'; result = FTPChdir(cip, file); if (result == kNoErr) { result = FTPFtwL2(cip, dir, cp, dirsize, proc, maxdepth - 1); if (result != kNoErr) break; if (FTPChdir(cip, "..") < 0) { result = kErrCannotGoToPrevDir; cip->errNo = kErrCannotGoToPrevDir; break; } } *end = '\0'; if (result != 0) break; } else { /* file */ cp = Strnpcat(dir, file, dirsize); result = (*proc)(cip, dir, kFtwFile); *end = '\0'; if (result != 0) break; } } DisposeLineListContents(&fileList); return (result); } /* FTPFtwL2 */ int FTPFtw(const FTPCIPtr cip, const char *const dir, FTPFtwProc proc, int maxdepth) { int result, result2; char *cp; char savedcwd[1024]; char curcwd[2048]; result = FTPIsDir(cip, dir); if (result < 0) { /* error */ return result; } else if (result == 0) { result = cip->errNo = kErrNotADirectory; return (result); } /* Preserve old working directory. */ (void) FTPGetCWD(cip, savedcwd, sizeof(savedcwd)); result = FTPChdir(cip, dir); if (result != kNoErr) { return (result); } /* Get full working directory we just changed to. */ result = FTPGetCWD(cip, curcwd, sizeof(curcwd) - 3); if (result != kNoErr) { if (FTPChdir(cip, savedcwd) != kNoErr) { result = kErrCannotGoToPrevDir; cip->errNo = kErrCannotGoToPrevDir; } return (result); } result2 = (*proc)(cip, curcwd, kFtwDir); if (result2 == kNoErr) { cp = curcwd + strlen(curcwd); if ((strchr(curcwd, '/') == NULL) && (strrchr(curcwd, '\\') != NULL)) *cp++ = '\\'; else *cp++ = '/'; *cp = '\0'; result = FTPFtwL2(cip, curcwd, cp, sizeof(curcwd), proc, maxdepth - 1); } if (FTPChdir(cip, savedcwd) != kNoErr) { /* Could not cd back to the original user directory -- bad. */ result = kErrCannotGoToPrevDir; cip->errNo = kErrCannotGoToPrevDir; return (result); } if ((result2 != kNoErr) && (result == kNoErr)) result = result2; return (result); } /* FTPFtw */ /* eof */