fix include file case
[reactos.git] / rosapps / net / ncftp / ncftp / ls.c
1 /* ls.c
2 *
3 * Copyright (c) 1992-2001 by Mike Gleason.
4 * All rights reserved.
5 *
6 */
7
8 #include "syshdrs.h"
9 #include "util.h"
10 #include "ls.h"
11 #include "trace.h"
12
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
16 * this period.
17 */
18 time_t gNowMinus6Mon, gNowPlus1Hr;
19
20 /* An array of month name abbreviations. This may not be in English. */
21 char gLsMon[13][4];
22
23 /* The program keeps its own cache of directory listings, so it doesn't
24 * need to re-request them from the server.
25 */
26 LsCacheItem gLsCache[kLsCacheSize];
27 int gOldestLsCacheItem;
28 int gLsCacheItemLifetime = kLsCacheItemLifetime;
29
30 extern FTPConnectionInfo gConn;
31 extern char gRemoteCWD[512];
32 extern int gScreenColumns, gDebug;
33
34
35 void
36 InitLsCache(void)
37 {
38 (void) memset(gLsCache, 0, sizeof(gLsCache));
39 gOldestLsCacheItem = 0;
40 } /* InitLsCache */
41
42
43
44 /* Creates the ls monthname abbreviation array, so we don't have to
45 * re-calculate them each time.
46 */
47 void InitLsMonths(void)
48 {
49 time_t now;
50 struct tm *ltp;
51 int i;
52
53 (void) time(&now);
54 ltp = localtime(&now); /* Fill up the structure. */
55 ltp->tm_mday = 15;
56 ltp->tm_hour = 12;
57 for (i=0; i<12; i++) {
58 ltp->tm_mon = i;
59 (void) strftime(gLsMon[i], sizeof(gLsMon[i]), "%b", ltp);
60 gLsMon[i][sizeof(gLsMon[i]) - 1] = '\0';
61 }
62 (void) strcpy(gLsMon[i], "BUG");
63 } /* InitLsMonths */
64
65
66
67
68 void InitLs(void)
69 {
70 InitLsCache();
71 InitLsMonths();
72 } /* InitLs */
73
74
75
76
77 /* Deletes an item from the ls cache. */
78 static void
79 FlushLsCacheItem(int i)
80 {
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 */
88
89
90
91
92 /* Clears all items from the ls cache. */
93 void
94 FlushLsCache(void)
95 {
96 int i;
97
98 for (i=0; i<kLsCacheSize; i++) {
99 if (gLsCache[i].expiration != (time_t) 0) {
100 FlushLsCacheItem(i);
101 }
102 }
103 } /* FlushLsCache */
104
105
106
107
108 /* Checks the cache for a directory listing for the given path. */
109 int
110 LsCacheLookup(const char *const itempath)
111 {
112 int i, j;
113 time_t now;
114
115 (void) time(&now);
116 for (i=0, j=gOldestLsCacheItem; i<kLsCacheSize; i++) {
117 if (--j < 0)
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. */
123 FlushLsCacheItem(j);
124 return (-1);
125 }
126 gLsCache[j].hits++;
127 return (j);
128 }
129 }
130 }
131 return (-1);
132 } /* LsCacheLookup */
133
134
135
136
137 /* Saves a directory listing from the given path into the cache. */
138 static void
139 LsCacheAdd(const char *const itempath, FileInfoListPtr files)
140 {
141 char *cp;
142 int j;
143
144 /* Never cache empty listings in case of errors */
145 if (files->nFileInfos == 0)
146 return;
147
148 j = LsCacheLookup(itempath);
149 if (j >= 0) {
150 /* Directory was already in there;
151 * Replace it with the new
152 * contents.
153 */
154 FlushLsCacheItem(j);
155 }
156
157 cp = StrDup(itempath);
158 if (cp == NULL)
159 return;
160
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);
168
169 /* Increment the pointer. This is a circular array, so if it
170 * hits the end it wraps over to the other side.
171 */
172 gOldestLsCacheItem++;
173 if (gOldestLsCacheItem >= kLsCacheSize)
174 gOldestLsCacheItem = 0;
175 } /* LsCacheAdd */
176
177
178
179
180 /* Does "ls -C", or the nice columnized /bin/ls-style format. */
181 static void
182 LsC(FileInfoListPtr dirp, int endChars, FILE *stream)
183 {
184 char buf[400];
185 char buf2[400];
186 int ncol, nrow;
187 int i, j, k, l;
188 int colw;
189 int n;
190 FileInfoVec itemv;
191 FileInfoPtr itemp;
192 char *cp1, *cp2, *lim;
193 int screenColumns;
194
195 screenColumns = gScreenColumns;
196 if (screenColumns > 400)
197 screenColumns = 400;
198 ncol = (screenColumns - 1) / ((int) dirp->maxFileLen + 2 + /*1or0*/ endChars);
199 if (ncol < 1)
200 ncol = 1;
201 colw = (screenColumns - 1) / ncol;
202 n = dirp->nFileInfos;
203 nrow = n / ncol;
204 if ((n % ncol) != 0)
205 nrow++;
206
207 for (i=0; i<(int) sizeof(buf2); i++)
208 buf2[i] = ' ';
209
210 itemv = dirp->vec;
211
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) {
215 if (k >= n)
216 continue;
217 itemp = itemv[k];
218 cp1 = buf + l;
219 lim = cp1 + (int) (itemp->relnameLen);
220 cp2 = itemp->relname;
221 while (cp1 < lim)
222 *cp1++ = *cp2++;
223 if (endChars != 0) {
224 if (itemp->type == 'l') {
225 /* Regular ls always uses @
226 * for a symlink tail, even if
227 * the linked item is a directory.
228 */
229 *cp1++ = '@';
230 } else if (itemp->type == 'd') {
231 *cp1++ = '/';
232 }
233 }
234 }
235 for (cp1 = buf + sizeof(buf); *--cp1 == ' '; ) {}
236 ++cp1;
237 *cp1++ = '\n';
238 *cp1 = '\0';
239 (void) fprintf(stream, "%s", buf);
240 Trace(0, "%s", buf);
241 }
242 } /* LsC */
243
244
245
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").
248 */
249 void
250 LsDate(char *dstr, time_t ts)
251 {
252 struct tm *gtp;
253
254 if (ts == kModTimeUnknown) {
255 (void) strcpy(dstr, " ");
256 return;
257 }
258 gtp = localtime(&ts);
259 if (gtp == NULL) {
260 (void) strcpy(dstr, "Jan 0 1900");
261 return;
262 }
263 if ((ts > gNowPlus1Hr) || (ts < gNowMinus6Mon)) {
264 (void) sprintf(dstr, "%s %2d %4d",
265 gLsMon[gtp->tm_mon],
266 gtp->tm_mday,
267 gtp->tm_year + 1900
268 );
269 } else {
270 (void) sprintf(dstr, "%s %2d %02d:%02d",
271 gLsMon[gtp->tm_mon],
272 gtp->tm_mday,
273 gtp->tm_hour,
274 gtp->tm_min
275 );
276 }
277 } /* LsDate */
278
279
280
281
282 /* Does "ls -l", or the detailed /bin/ls-style, one file per line . */
283 void
284 LsL(FileInfoListPtr dirp, int endChars, int linkedTo, FILE *stream)
285 {
286 FileInfoPtr diritemp;
287 FileInfoVec diritemv;
288 int i;
289 char fTail[2];
290 int fType;
291 const char *l1, *l2;
292 char datestr[16];
293 char sizestr[32];
294 char plugspec[16];
295 char plugstr[64];
296 const char *expad;
297
298 fTail[0] = '\0';
299 fTail[1] = '\0';
300
301 (void) time(&gNowPlus1Hr);
302 gNowMinus6Mon = gNowPlus1Hr - 15552000;
303 gNowPlus1Hr += 3600;
304
305 diritemv = dirp->vec;
306 #ifdef HAVE_SNPRINTF
307 (void) snprintf(
308 plugspec,
309 sizeof(plugspec) - 1,
310 #else
311 (void) sprintf(
312 plugspec,
313 #endif
314 "%%-%ds",
315 (int) dirp->maxPlugLen
316 );
317
318 if (dirp->maxPlugLen < 29) {
319 /* We have some extra space to work with,
320 * so we can space out the columns a little.
321 */
322 expad = " ";
323 } else {
324 expad = "";
325 }
326
327 for (i=0; ; i++) {
328 diritemp = diritemv[i];
329 if (diritemp == NULL)
330 break;
331
332 fType = (int) diritemp->type;
333 if (endChars != 0) {
334 if (fType == 'd')
335 fTail[0] = '/';
336 else
337 fTail[0] = '\0';
338 }
339
340 if (diritemp->rlinkto != NULL) {
341 if (linkedTo != 0) {
342 l1 = "";
343 l2 = "";
344 } else {
345 l1 = " -> ";
346 l2 = diritemp->rlinkto;
347 }
348 } else {
349 l1 = "";
350 l2 = "";
351 }
352
353 LsDate(datestr, diritemp->mdtm);
354
355 if (diritemp->size == kSizeUnknown) {
356 *sizestr = '\0';
357 } else {
358 #ifdef HAVE_SNPRINTF
359 (void) snprintf(
360 sizestr,
361 sizeof(sizestr) - 1,
362 #else
363 (void) sprintf(
364 sizestr,
365 #endif
366 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG)
367 PRINTF_LONG_LONG,
368 #else
369 "%ld",
370 #endif
371 (longest_int) diritemp->size
372 );
373 }
374
375 #ifdef HAVE_SNPRINTF
376 (void) snprintf(
377 plugstr,
378 sizeof(plugstr) - 1,
379 #else
380 (void) sprintf(
381 plugstr,
382 #endif
383 plugspec,
384 diritemp->plug
385 );
386
387 (void) fprintf(stream, "%s %12s %s%s %s%s%s%s%s\n",
388 plugstr,
389 sizestr,
390 expad,
391 datestr,
392 expad,
393 diritemp->relname,
394 l1,
395 l2,
396 fTail
397 );
398 Trace(0, "%s %12s %s%s %s%s%s%s%s\n",
399 plugstr,
400 sizestr,
401 expad,
402 datestr,
403 expad,
404 diritemp->relname,
405 l1,
406 l2,
407 fTail
408 );
409 }
410 } /* LsL */
411
412
413
414
415 /* Does "ls -1", or the simple single-column /bin/ls-style format, with
416 * one file per line.
417 */
418 void
419 Ls1(FileInfoListPtr dirp, int endChars, FILE *stream)
420 {
421 char fTail[2];
422 int i;
423 int fType;
424 FileInfoVec diritemv;
425 FileInfoPtr diritemp;
426
427 fTail[0] = '\0';
428 fTail[1] = '\0';
429 diritemv = dirp->vec;
430
431 for (i=0; ; i++) {
432 diritemp = diritemv[i];
433 if (diritemp == NULL)
434 break;
435
436 fType = (int) diritemp->type;
437 if (endChars != 0) {
438 if (fType == 'd')
439 fTail[0] = '/';
440 else
441 fTail[0] = '\0';
442 }
443
444 (void) fprintf(stream, "%s%s\n",
445 diritemp->relname,
446 fTail
447 );
448
449 Trace(0, "%s%s\n",
450 diritemp->relname,
451 fTail
452 );
453 }
454 } /* Ls1 */
455
456
457
458
459
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.
463 */
464 void
465 Ls(const char *const item, int listmode, const char *const options, FILE *stream)
466 {
467 char itempath[512];
468 FileInfoList fil;
469 FileInfoListPtr filp;
470 LinePtr linePtr, nextLinePtr;
471 LineList dirContents;
472 int parsed;
473 int linkedTo;
474 int endChars;
475 int rlisted;
476 int opt;
477 const char *cp;
478 int sortBy;
479 int sortOrder;
480 int unknownOpts;
481 char optstr[32];
482 char unoptstr[32];
483 int doNotUseCache;
484 int wasInCache;
485 int mlsd;
486 int ci;
487
488 InitLineList(&dirContents);
489 InitFileInfoList(&fil);
490
491 sortBy = 'n'; /* Sort by filename. */
492 sortOrder = 'a'; /* Sort in ascending order. */
493 linkedTo = 0;
494 endChars = (listmode == 'C') ? 1 : 0;
495 unknownOpts = 0;
496 memset(unoptstr, 0, sizeof(unoptstr));
497 unoptstr[0] = '-';
498 doNotUseCache = 0;
499 rlisted = 0;
500
501 for (cp = options; *cp != '\0'; cp++) {
502 opt = *cp;
503 switch (opt) {
504 case 't':
505 sortBy = 't'; /* Sort by modification time. */
506 break;
507 case 'S':
508 sortBy = 's'; /* Sort by size. */
509 break;
510 case 'r':
511 sortOrder = 'd'; /* descending order */
512 break;
513 case 'L':
514 linkedTo = 1;
515 break;
516 case 'f':
517 doNotUseCache = 1;
518 break;
519 case 'F':
520 case 'p':
521 endChars = 1;
522 break;
523 case '1':
524 case 'C':
525 case 'l':
526 listmode = opt;
527 break;
528 case '-':
529 break;
530 default:
531 if (unknownOpts < ((int) sizeof(unoptstr) - 2))
532 unoptstr[unknownOpts + 1] = opt;
533 unknownOpts++;
534 break;
535 }
536 }
537
538 /* Create a possibly relative path into an absolute path. */
539 PathCat(itempath, sizeof(itempath), gRemoteCWD,
540 (item == NULL) ? "." : item);
541
542 if (unknownOpts > 0) {
543 /* Can't handle these -- pass them through
544 * to the server.
545 */
546
547 Trace(0, "ls caching not used because of ls flags: %s\n", unoptstr);
548 optstr[0] = '-';
549 optstr[1] = listmode;
550 optstr[2] = '\0';
551 (void) STRNCAT(optstr, options);
552 if ((FTPListToMemory2(&gConn, (item == NULL) ? "" : item, &dirContents, optstr, 1, 0)) < 0) {
553 if (stream != NULL)
554 (void) fprintf(stderr, "List failed.\n");
555 return;
556 }
557
558 rlisted = 1;
559 parsed = -1;
560 wasInCache = 0;
561 filp = NULL;
562 } else if ((doNotUseCache != 0) || ((ci = LsCacheLookup(itempath)) < 0)) {
563 /* Not in cache. */
564 wasInCache = 0;
565
566 mlsd = 1;
567 if ((FTPListToMemory2(&gConn, (item == NULL) ? "" : item, &dirContents, "-l", 1, &mlsd)) < 0) {
568 if (stream != NULL)
569 (void) fprintf(stderr, "List failed.\n");
570 return;
571 }
572
573 rlisted = 1;
574 filp = &fil;
575 if (mlsd != 0) {
576 parsed = UnMlsD(filp, &dirContents);
577 if (parsed < 0) {
578 Trace(0, "UnMlsD: %d\n", parsed);
579 }
580 } else {
581 parsed = UnLslR(filp, &dirContents, gConn.serverType);
582 if (parsed < 0) {
583 Trace(0, "UnLslR: %d\n", parsed);
584 }
585 }
586 if (parsed >= 0) {
587 VectorizeFileInfoList(filp);
588 if (filp->vec == NULL) {
589 if (stream != NULL)
590 (void) fprintf(stderr, "List processing failed.\n");
591 return;
592 }
593 }
594 } else {
595 filp = &gLsCache[ci].fil;
596 wasInCache = 1;
597 parsed = 1;
598 Trace(0, "ls cache hit: %s\n", itempath);
599 }
600
601 if (rlisted != 0) {
602 Trace(0, "Remote listing contents {\n");
603 for (linePtr = dirContents.first;
604 linePtr != NULL;
605 linePtr = nextLinePtr)
606 {
607 nextLinePtr = linePtr->next;
608 Trace(0, " %s\n", linePtr->line);
609 }
610 Trace(0, "}\n");
611 }
612
613 if (parsed >= 0) {
614 SortFileInfoList(filp, sortBy, sortOrder);
615 if (stream != NULL) {
616 if (listmode == 'l')
617 LsL(filp, endChars, linkedTo, stream);
618 else if (listmode == '1')
619 Ls1(filp, endChars, stream);
620 else
621 LsC(filp, endChars, stream);
622 }
623 if (wasInCache == 0) {
624 LsCacheAdd(itempath, filp);
625 }
626 } else if (stream != NULL) {
627 for (linePtr = dirContents.first;
628 linePtr != NULL;
629 linePtr = nextLinePtr)
630 {
631 nextLinePtr = linePtr->next;
632 (void) fprintf(stream, "%s\n", linePtr->line);
633 Trace(0, " %s\n", linePtr->line);
634 }
635 }
636
637 DisposeLineListContents(&dirContents);
638 } /* Ls */
639
640
641
642 #if defined(WIN32) || defined(_WINDOWS)
643 /* Prints a local directory listing in the specified format on the specified
644 * output stream.
645 */
646 void
647 LLs(const char *const item, int listmode, const char *const options, FILE *stream)
648 {
649 char itempath[512];
650 int linkedTo;
651 int endChars;
652 int opt;
653 const char *cp;
654 int sortBy;
655 int sortOrder;
656 int unknownOpts;
657 char unoptstr[32];
658 LineList ll;
659 FileInfoPtr fip, fip2;
660 FileInfoList fil;
661 struct Stat st;
662 int result;
663 size_t len;
664
665 InitLineList(&ll);
666 InitFileInfoList(&fil);
667
668 sortBy = 'n'; /* Sort by filename. */
669 sortOrder = 'a'; /* Sort in ascending order. */
670 linkedTo = 0;
671 endChars = (listmode == 'C') ? 1 : 0;
672 unknownOpts = 0;
673 memset(unoptstr, 0, sizeof(unoptstr));
674 unoptstr[0] = '-';
675
676 for (cp = options; *cp != '\0'; cp++) {
677 opt = *cp;
678 switch (opt) {
679 case 't':
680 sortBy = 't'; /* Sort by modification time. */
681 break;
682 case 'S':
683 sortBy = 's'; /* Sort by size. */
684 break;
685 case 'r':
686 sortOrder = 'd'; /* descending order */
687 break;
688 case 'L':
689 linkedTo = 1;
690 break;
691 case 'f':
692 break;
693 case 'F':
694 case 'p':
695 endChars = 1;
696 break;
697 case '1':
698 case 'C':
699 case 'l':
700 listmode = opt;
701 break;
702 case '-':
703 break;
704 default:
705 if (unknownOpts < ((int) sizeof(unoptstr) - 2))
706 unoptstr[unknownOpts + 1] = opt;
707 unknownOpts++;
708 break;
709 }
710 }
711
712 if ((item == NULL) || (strcmp(item, ".") == 0))
713 STRNCPY(itempath, "*.*");
714 else {
715 STRNCPY(itempath, item);
716 if (strpbrk(itempath, "*?") == NULL)
717 STRNCAT(itempath, "\\*.*");
718 }
719
720 InitLineList(&ll);
721 result = FTPLocalGlob(&gConn, &ll, itempath, kGlobYes);
722 if (result < 0) {
723 FTPPerror(&gConn, result, kErrGlobFailed, "local glob", itempath);
724 DisposeLineListContents(&ll);
725 return;
726 }
727 if (LineListToFileInfoList(&ll, &fil) < 0)
728 return;
729 DisposeLineListContents(&ll);
730
731 for (fip = fil.first; fip != NULL; fip = fip2) {
732 fip2 = fip->next;
733 if (Stat(fip->relname, &st) < 0) {
734 fip2 = RemoveFileInfo(&fil, fip);
735 continue;
736 }
737 cp = StrRFindLocalPathDelim(fip->relname);
738 if (cp != NULL) {
739 /* FTPLocalGlob will tack on the pathnames too,
740 * which we don't want for this hack.
741 */
742 cp++;
743 len = strlen(cp);
744 memmove(fip->relname, cp, len + 1);
745 } else {
746 len = strlen(fip->relname);
747 }
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)) {
755 fip->type = 'd';
756 fip->plug[0] = 'd';
757 } else {
758 fip->type = '-';
759 fip->size = st.st_size;
760 }
761 fip->mdtm = st.st_mtime;
762 }
763 fil.maxPlugLen = strlen("---------- 1 user group");
764
765 VectorizeFileInfoList(&fil);
766 SortFileInfoList(&fil, sortBy, sortOrder);
767 if (stream != NULL) {
768 if (listmode == 'l')
769 LsL(&fil, endChars, linkedTo, stream);
770 else if (listmode == '1')
771 Ls1(&fil, endChars, stream);
772 else
773 LsC(&fil, endChars, stream);
774 }
775
776 DisposeFileInfoListContents(&fil);
777 } /* LLs */
778 #endif
779