3 * Copyright (c) 1992-2001 by Mike Gleason.
13 /* The program keeps a timestamp of 6 months ago and an hour from now, because
14 * the standard /bin/ls command will print the time (i.e. "Nov 8 09:20")
15 * instead of the year (i.e. "Oct 27 1996") if a file's timestamp is within
18 time_t gNowMinus6Mon
, gNowPlus1Hr
;
20 /* An array of month name abbreviations. This may not be in English. */
23 /* The program keeps its own cache of directory listings, so it doesn't
24 * need to re-request them from the server.
26 LsCacheItem gLsCache
[kLsCacheSize
];
27 int gOldestLsCacheItem
;
28 int gLsCacheItemLifetime
= kLsCacheItemLifetime
;
30 extern FTPConnectionInfo gConn
;
31 extern char gRemoteCWD
[512];
32 extern int gScreenColumns
, gDebug
;
38 (void) memset(gLsCache
, 0, sizeof(gLsCache
));
39 gOldestLsCacheItem
= 0;
44 /* Creates the ls monthname abbreviation array, so we don't have to
45 * re-calculate them each time.
47 void InitLsMonths(void)
54 ltp
= localtime(&now
); /* Fill up the structure. */
57 for (i
=0; i
<12; i
++) {
59 (void) strftime(gLsMon
[i
], sizeof(gLsMon
[i
]), "%b", ltp
);
60 gLsMon
[i
][sizeof(gLsMon
[i
]) - 1] = '\0';
62 (void) strcpy(gLsMon
[i
], "BUG");
77 /* Deletes an item from the ls cache. */
79 FlushLsCacheItem(int i
)
81 Trace(1, "flush ls cache item: %s\n", gLsCache
[i
].itempath
);
82 if (gLsCache
[i
].itempath
!= NULL
)
83 free(gLsCache
[i
].itempath
);
84 gLsCache
[i
].itempath
= NULL
;
85 gLsCache
[i
].expiration
= (time_t) 0;
86 DisposeFileInfoListContents(&gLsCache
[i
].fil
);
87 } /* FlushLsCacheItem */
92 /* Clears all items from the ls cache. */
98 for (i
=0; i
<kLsCacheSize
; i
++) {
99 if (gLsCache
[i
].expiration
!= (time_t) 0) {
108 /* Checks the cache for a directory listing for the given path. */
110 LsCacheLookup(const char *const itempath
)
116 for (i
=0, j
=gOldestLsCacheItem
; i
<kLsCacheSize
; i
++) {
118 j
= kLsCacheSize
- 1;
119 if ((gLsCache
[j
].expiration
!= (time_t) 0) && (gLsCache
[j
].itempath
!= NULL
)) {
120 if (strcmp(itempath
, gLsCache
[j
].itempath
) == 0) {
121 if (now
> gLsCache
[j
].expiration
) {
122 /* Found it, but it was expired. */
132 } /* LsCacheLookup */
137 /* Saves a directory listing from the given path into the cache. */
139 LsCacheAdd(const char *const itempath
, FileInfoListPtr files
)
144 /* Never cache empty listings in case of errors */
145 if (files
->nFileInfos
== 0)
148 j
= LsCacheLookup(itempath
);
150 /* Directory was already in there;
151 * Replace it with the new
157 cp
= StrDup(itempath
);
161 j
= gOldestLsCacheItem
;
162 (void) memcpy(&gLsCache
[j
].fil
, files
, sizeof(FileInfoList
));
163 (void) time(&gLsCache
[j
].expiration
);
164 gLsCache
[j
].expiration
+= gLsCacheItemLifetime
;
165 gLsCache
[j
].hits
= 0;
166 gLsCache
[j
].itempath
= cp
;
167 Trace(1, "ls cache add: %s\n", itempath
);
169 /* Increment the pointer. This is a circular array, so if it
170 * hits the end it wraps over to the other side.
172 gOldestLsCacheItem
++;
173 if (gOldestLsCacheItem
>= kLsCacheSize
)
174 gOldestLsCacheItem
= 0;
180 /* Does "ls -C", or the nice columnized /bin/ls-style format. */
182 LsC(FileInfoListPtr dirp
, int endChars
, FILE *stream
)
192 char *cp1
, *cp2
, *lim
;
195 screenColumns
= gScreenColumns
;
196 if (screenColumns
> 400)
198 ncol
= (screenColumns
- 1) / ((int) dirp
->maxFileLen
+ 2 + /*1or0*/ endChars
);
201 colw
= (screenColumns
- 1) / ncol
;
202 n
= dirp
->nFileInfos
;
207 for (i
=0; i
<(int) sizeof(buf2
); i
++)
212 for (j
=0; j
<nrow
; j
++) {
213 (void) memcpy(buf
, buf2
, sizeof(buf
));
214 for (i
=0, k
=j
, l
=0; i
<ncol
; i
++, k
+= nrow
, l
+= colw
) {
219 lim
= cp1
+ (int) (itemp
->relnameLen
);
220 cp2
= itemp
->relname
;
224 if (itemp
->type
== 'l') {
225 /* Regular ls always uses @
226 * for a symlink tail, even if
227 * the linked item is a directory.
230 } else if (itemp
->type
== 'd') {
235 for (cp1
= buf
+ sizeof(buf
); *--cp1
== ' '; ) {}
239 (void) fprintf(stream
, "%s", buf
);
246 /* Converts a timestamp into a recent date string ("May 27 06:33"), or an
247 * old (or future) date string (i.e. "Oct 27 1996").
250 LsDate(char *dstr
, time_t ts
)
254 if (ts
== kModTimeUnknown
) {
255 (void) strcpy(dstr
, " ");
258 gtp
= localtime(&ts
);
260 (void) strcpy(dstr
, "Jan 0 1900");
263 if ((ts
> gNowPlus1Hr
) || (ts
< gNowMinus6Mon
)) {
264 (void) sprintf(dstr
, "%s %2d %4d",
270 (void) sprintf(dstr
, "%s %2d %02d:%02d",
282 /* Does "ls -l", or the detailed /bin/ls-style, one file per line . */
284 LsL(FileInfoListPtr dirp
, int endChars
, int linkedTo
, FILE *stream
)
286 FileInfoPtr diritemp
;
287 FileInfoVec diritemv
;
301 (void) time(&gNowPlus1Hr
);
302 gNowMinus6Mon
= gNowPlus1Hr
- 15552000;
305 diritemv
= dirp
->vec
;
309 sizeof(plugspec
) - 1,
315 (int) dirp
->maxPlugLen
318 if (dirp
->maxPlugLen
< 29) {
319 /* We have some extra space to work with,
320 * so we can space out the columns a little.
328 diritemp
= diritemv
[i
];
329 if (diritemp
== NULL
)
332 fType
= (int) diritemp
->type
;
340 if (diritemp
->rlinkto
!= NULL
) {
346 l2
= diritemp
->rlinkto
;
353 LsDate(datestr
, diritemp
->mdtm
);
355 if (diritemp
->size
== kSizeUnknown
) {
366 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG)
371 (longest_int
) diritemp
->size
387 (void) fprintf(stream
, "%s %12s %s%s %s%s%s%s%s\n",
398 Trace(0, "%s %12s %s%s %s%s%s%s%s\n",
415 /* Does "ls -1", or the simple single-column /bin/ls-style format, with
419 Ls1(FileInfoListPtr dirp
, int endChars
, FILE *stream
)
424 FileInfoVec diritemv
;
425 FileInfoPtr diritemp
;
429 diritemv
= dirp
->vec
;
432 diritemp
= diritemv
[i
];
433 if (diritemp
== NULL
)
436 fType
= (int) diritemp
->type
;
444 (void) fprintf(stream
, "%s%s\n",
460 /* Prints a directory listing in the specified format on the specified
461 * output stream. It may or may not need to request it from the remote
462 * server, depending on whether it was cached.
465 Ls(const char *const item
, int listmode
, const char *const options
, FILE *stream
)
469 FileInfoListPtr filp
;
470 LinePtr linePtr
, nextLinePtr
;
471 LineList dirContents
;
488 InitLineList(&dirContents
);
489 InitFileInfoList(&fil
);
491 sortBy
= 'n'; /* Sort by filename. */
492 sortOrder
= 'a'; /* Sort in ascending order. */
494 endChars
= (listmode
== 'C') ? 1 : 0;
496 memset(unoptstr
, 0, sizeof(unoptstr
));
501 for (cp
= options
; *cp
!= '\0'; cp
++) {
505 sortBy
= 't'; /* Sort by modification time. */
508 sortBy
= 's'; /* Sort by size. */
511 sortOrder
= 'd'; /* descending order */
531 if (unknownOpts
< ((int) sizeof(unoptstr
) - 2))
532 unoptstr
[unknownOpts
+ 1] = opt
;
538 /* Create a possibly relative path into an absolute path. */
539 PathCat(itempath
, sizeof(itempath
), gRemoteCWD
,
540 (item
== NULL
) ? "." : item
);
542 if (unknownOpts
> 0) {
543 /* Can't handle these -- pass them through
547 Trace(0, "ls caching not used because of ls flags: %s\n", unoptstr
);
549 optstr
[1] = listmode
;
551 (void) STRNCAT(optstr
, options
);
552 if ((FTPListToMemory2(&gConn
, (item
== NULL
) ? "" : item
, &dirContents
, optstr
, 1, 0)) < 0) {
554 (void) fprintf(stderr
, "List failed.\n");
562 } else if ((doNotUseCache
!= 0) || ((ci
= LsCacheLookup(itempath
)) < 0)) {
567 if ((FTPListToMemory2(&gConn
, (item
== NULL
) ? "" : item
, &dirContents
, "-l", 1, &mlsd
)) < 0) {
569 (void) fprintf(stderr
, "List failed.\n");
576 parsed
= UnMlsD(filp
, &dirContents
);
578 Trace(0, "UnMlsD: %d\n", parsed
);
581 parsed
= UnLslR(filp
, &dirContents
, gConn
.serverType
);
583 Trace(0, "UnLslR: %d\n", parsed
);
587 VectorizeFileInfoList(filp
);
588 if (filp
->vec
== NULL
) {
590 (void) fprintf(stderr
, "List processing failed.\n");
595 filp
= &gLsCache
[ci
].fil
;
598 Trace(0, "ls cache hit: %s\n", itempath
);
602 Trace(0, "Remote listing contents {\n");
603 for (linePtr
= dirContents
.first
;
605 linePtr
= nextLinePtr
)
607 nextLinePtr
= linePtr
->next
;
608 Trace(0, " %s\n", linePtr
->line
);
614 SortFileInfoList(filp
, sortBy
, sortOrder
);
615 if (stream
!= NULL
) {
617 LsL(filp
, endChars
, linkedTo
, stream
);
618 else if (listmode
== '1')
619 Ls1(filp
, endChars
, stream
);
621 LsC(filp
, endChars
, stream
);
623 if (wasInCache
== 0) {
624 LsCacheAdd(itempath
, filp
);
626 } else if (stream
!= NULL
) {
627 for (linePtr
= dirContents
.first
;
629 linePtr
= nextLinePtr
)
631 nextLinePtr
= linePtr
->next
;
632 (void) fprintf(stream
, "%s\n", linePtr
->line
);
633 Trace(0, " %s\n", linePtr
->line
);
637 DisposeLineListContents(&dirContents
);
642 #if defined(WIN32) || defined(_WINDOWS)
643 /* Prints a local directory listing in the specified format on the specified
647 LLs(const char *const item
, int listmode
, const char *const options
, FILE *stream
)
659 FileInfoPtr fip
, fip2
;
666 InitFileInfoList(&fil
);
668 sortBy
= 'n'; /* Sort by filename. */
669 sortOrder
= 'a'; /* Sort in ascending order. */
671 endChars
= (listmode
== 'C') ? 1 : 0;
673 memset(unoptstr
, 0, sizeof(unoptstr
));
676 for (cp
= options
; *cp
!= '\0'; cp
++) {
680 sortBy
= 't'; /* Sort by modification time. */
683 sortBy
= 's'; /* Sort by size. */
686 sortOrder
= 'd'; /* descending order */
705 if (unknownOpts
< ((int) sizeof(unoptstr
) - 2))
706 unoptstr
[unknownOpts
+ 1] = opt
;
712 if ((item
== NULL
) || (strcmp(item
, ".") == 0))
713 STRNCPY(itempath
, "*.*");
715 STRNCPY(itempath
, item
);
716 if (strpbrk(itempath
, "*?") == NULL
)
717 STRNCAT(itempath
, "\\*.*");
721 result
= FTPLocalGlob(&gConn
, &ll
, itempath
, kGlobYes
);
723 FTPPerror(&gConn
, result
, kErrGlobFailed
, "local glob", itempath
);
724 DisposeLineListContents(&ll
);
727 if (LineListToFileInfoList(&ll
, &fil
) < 0)
729 DisposeLineListContents(&ll
);
731 for (fip
= fil
.first
; fip
!= NULL
; fip
= fip2
) {
733 if (Stat(fip
->relname
, &st
) < 0) {
734 fip2
= RemoveFileInfo(&fil
, fip
);
737 cp
= StrRFindLocalPathDelim(fip
->relname
);
739 /* FTPLocalGlob will tack on the pathnames too,
740 * which we don't want for this hack.
744 memmove(fip
->relname
, cp
, len
+ 1);
746 len
= strlen(fip
->relname
);
748 if (len
> fil
.maxFileLen
)
749 fil
.maxFileLen
= len
;
750 fip
->relnameLen
= len
;
751 fip
->rname
= StrDup(fip
->relname
);
752 fip
->lname
= StrDup(fip
->relname
);
753 fip
->plug
= StrDup("---------- 1 user group");
754 if (S_ISDIR(st
.st_mode
)) {
759 fip
->size
= st
.st_size
;
761 fip
->mdtm
= st
.st_mtime
;
763 fil
.maxPlugLen
= strlen("---------- 1 user group");
765 VectorizeFileInfoList(&fil
);
766 SortFileInfoList(&fil
, sortBy
, sortOrder
);
767 if (stream
!= NULL
) {
769 LsL(&fil
, endChars
, linkedTo
, stream
);
770 else if (listmode
== '1')
771 Ls1(&fil
, endChars
, stream
);
773 LsC(&fil
, endChars
, stream
);
776 DisposeFileInfoListContents(&fil
);