xref: /freebsd/crypto/heimdal/appl/ftp/ftpd/ls.c (revision 817420dc8eac7df799c78f5309b75092b7f7cd40)
1 /*
2  * Copyright (c) 1999 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of KTH nor the names of its contributors may be
18  *    used to endorse or promote products derived from this software without
19  *    specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32 
33 #include "ftpd_locl.h"
34 
35 RCSID("$Id: ls.c,v 1.14 2000/01/05 13:48:58 joda Exp $");
36 
37 struct fileinfo {
38     struct stat st;
39     int inode;
40     int bsize;
41     char mode[11];
42     int n_link;
43     char *user;
44     char *group;
45     char *size;
46     char *major;
47     char *minor;
48     char *date;
49     char *filename;
50     char *link;
51 };
52 
53 static void
54 free_fileinfo(struct fileinfo *f)
55 {
56     free(f->user);
57     free(f->group);
58     free(f->size);
59     free(f->major);
60     free(f->minor);
61     free(f->date);
62     free(f->filename);
63     free(f->link);
64 }
65 
66 #define LS_DIRS		 1
67 #define LS_IGNORE_DOT	 2
68 #define LS_SORT_MODE	 12
69 #define SORT_MODE(f) ((f) & LS_SORT_MODE)
70 #define LS_SORT_NAME	 4
71 #define LS_SORT_MTIME	 8
72 #define LS_SORT_SIZE	12
73 #define LS_SORT_REVERSE	16
74 
75 #define LS_SIZE		32
76 #define LS_INODE	64
77 
78 #ifndef S_ISTXT
79 #define S_ISTXT S_ISVTX
80 #endif
81 
82 #ifndef S_ISSOCK
83 #define S_ISSOCK(mode)  (((mode) & _S_IFMT) == S_IFSOCK)
84 #endif
85 
86 #ifndef S_ISLNK
87 #define S_ISLNK(mode)   (((mode) & _S_IFMT) == S_IFLNK)
88 #endif
89 
90 static void
91 make_fileinfo(const char *filename, struct fileinfo *file, int flags)
92 {
93     char buf[128];
94     struct stat *st = &file->st;
95 
96     file->inode = st->st_ino;
97 #ifdef S_BLKSIZE
98     file->bsize = st->st_blocks * S_BLKSIZE / 1024;
99 #else
100     file->bsize = st->st_blocks * 512 / 1024;
101 #endif
102 
103     if(S_ISDIR(st->st_mode))
104 	file->mode[0] = 'd';
105     else if(S_ISCHR(st->st_mode))
106 	file->mode[0] = 'c';
107     else if(S_ISBLK(st->st_mode))
108 	file->mode[0] = 'b';
109     else if(S_ISREG(st->st_mode))
110 	file->mode[0] = '-';
111     else if(S_ISFIFO(st->st_mode))
112 	file->mode[0] = 'p';
113     else if(S_ISLNK(st->st_mode))
114 	file->mode[0] = 'l';
115     else if(S_ISSOCK(st->st_mode))
116 	file->mode[0] = 's';
117 #ifdef S_ISWHT
118     else if(S_ISWHT(st->st_mode))
119 	file->mode[0] = 'w';
120 #endif
121     else
122 	file->mode[0] = '?';
123     {
124 	char *x[] = { "---", "--x", "-w-", "-wx",
125 		      "r--", "r-x", "rw-", "rwx" };
126 	strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);
127 	strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);
128 	strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);
129 	if((st->st_mode & S_ISUID)) {
130 	    if((st->st_mode & S_IXUSR))
131 		file->mode[3] = 's';
132 	    else
133 		file->mode[3] = 'S';
134 	}
135 	if((st->st_mode & S_ISGID)) {
136 	    if((st->st_mode & S_IXGRP))
137 		file->mode[6] = 's';
138 	    else
139 		file->mode[6] = 'S';
140 	}
141 	if((st->st_mode & S_ISTXT)) {
142 	    if((st->st_mode & S_IXOTH))
143 		file->mode[9] = 't';
144 	    else
145 		file->mode[9] = 'T';
146 	}
147     }
148     file->n_link = st->st_nlink;
149     {
150 	struct passwd *pwd;
151 	pwd = getpwuid(st->st_uid);
152 	if(pwd == NULL)
153 	    asprintf(&file->user, "%u", (unsigned)st->st_uid);
154 	else
155 	    file->user = strdup(pwd->pw_name);
156     }
157     {
158 	struct group *grp;
159 	grp = getgrgid(st->st_gid);
160 	if(grp == NULL)
161 	    asprintf(&file->group, "%u", (unsigned)st->st_gid);
162 	else
163 	    file->group = strdup(grp->gr_name);
164     }
165 
166     if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
167 #if defined(major) && defined(minor)
168 	asprintf(&file->major, "%u", (unsigned)major(st->st_rdev));
169 	asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev));
170 #else
171 	/* Don't want to use the DDI/DKI crap. */
172 	asprintf(&file->major, "%u", (unsigned)st->st_rdev);
173 	asprintf(&file->minor, "%u", 0);
174 #endif
175     } else
176 	asprintf(&file->size, "%lu", (unsigned long)st->st_size);
177 
178     {
179 	time_t t = time(NULL);
180 	struct tm *tm = localtime(&st->st_mtime);
181 	if((t - st->st_mtime > 6*30*24*60*60) ||
182 	   (st->st_mtime - t > 6*30*24*60*60))
183 	    strftime(buf, sizeof(buf), "%b %e  %Y", tm);
184 	else
185 	    strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
186 	file->date = strdup(buf);
187     }
188     {
189 	const char *p = strrchr(filename, '/');
190 	if(p)
191 	    p++;
192 	else
193 	    p = filename;
194 	file->filename = strdup(p);
195     }
196     if(S_ISLNK(st->st_mode)) {
197 	int n;
198 	n = readlink((char *)filename, buf, sizeof(buf));
199 	if(n >= 0) {
200 	    buf[n] = '\0';
201 	    file->link = strdup(buf);
202 	} else
203 	    warn("%s: readlink", filename);
204     }
205 }
206 
207 static void
208 print_file(FILE *out,
209 	   int flags,
210 	   struct fileinfo *f,
211 	   int max_inode,
212 	   int max_bsize,
213 	   int max_n_link,
214 	   int max_user,
215 	   int max_group,
216 	   int max_size,
217 	   int max_major,
218 	   int max_minor,
219 	   int max_date)
220 {
221     if(f->filename == NULL)
222 	return;
223 
224     if(flags & LS_INODE) {
225 	sec_fprintf2(out, "%*d", max_inode, f->inode);
226 	sec_fprintf2(out, "  ");
227     }
228     if(flags & LS_SIZE) {
229 	sec_fprintf2(out, "%*d", max_bsize, f->bsize);
230 	sec_fprintf2(out, "  ");
231     }
232     sec_fprintf2(out, "%s", f->mode);
233     sec_fprintf2(out, "  ");
234     sec_fprintf2(out, "%*d", max_n_link, f->n_link);
235     sec_fprintf2(out, " ");
236     sec_fprintf2(out, "%-*s", max_user, f->user);
237     sec_fprintf2(out, "  ");
238     sec_fprintf2(out, "%-*s", max_group, f->group);
239     sec_fprintf2(out, "  ");
240     if(f->major != NULL && f->minor != NULL)
241 	sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);
242     else
243 	sec_fprintf2(out, "%*s", max_size, f->size);
244     sec_fprintf2(out, " ");
245     sec_fprintf2(out, "%*s", max_date, f->date);
246     sec_fprintf2(out, " ");
247     sec_fprintf2(out, "%s", f->filename);
248     if(f->link)
249 	sec_fprintf2(out, " -> %s", f->link);
250     sec_fprintf2(out, "\r\n");
251 }
252 
253 static int
254 compare_filename(struct fileinfo *a, struct fileinfo *b)
255 {
256     if(a->filename == NULL)
257 	return 1;
258     if(b->filename == NULL)
259 	return -1;
260     return strcmp(a->filename, b->filename);
261 }
262 
263 static int
264 compare_mtime(struct fileinfo *a, struct fileinfo *b)
265 {
266     if(a->filename == NULL)
267 	return 1;
268     if(b->filename == NULL)
269 	return -1;
270     return a->st.st_mtime - b->st.st_mtime;
271 }
272 
273 static int
274 compare_size(struct fileinfo *a, struct fileinfo *b)
275 {
276     if(a->filename == NULL)
277 	return 1;
278     if(b->filename == NULL)
279 	return -1;
280     return a->st.st_size - b->st.st_size;
281 }
282 
283 static void
284 list_dir(FILE *out, const char *directory, int flags);
285 
286 static int
287 log10(int num)
288 {
289     int i = 1;
290     while(num > 10) {
291 	i++;
292 	num /= 10;
293     }
294     return i;
295 }
296 
297 /*
298  * Operate as lstat but fake up entries for AFS mount points so we don't
299  * have to fetch them.
300  */
301 
302 static int
303 lstat_file (const char *file, struct stat *sb)
304 {
305 #ifdef KRB4
306     if (k_hasafs()
307 	&& strcmp(file, ".")
308 	&& strcmp(file, ".."))
309     {
310 	struct ViceIoctl    a_params;
311 	char               *last;
312 	char               *path_bkp;
313 	static ino_t	   ino_counter = 0, ino_last = 0;
314 	int		   ret;
315 	const int	   maxsize = 2048;
316 
317 	path_bkp = strdup (file);
318 	if (path_bkp == NULL)
319 	    return -1;
320 
321 	a_params.out = malloc (maxsize);
322 	if (a_params.out == NULL) {
323 	    free (path_bkp);
324 	    return -1;
325 	}
326 
327 	/* If path contains more than the filename alone - split it */
328 
329 	last = strrchr (path_bkp, '/');
330 	if (last != NULL) {
331 	    *last = '\0';
332 	    a_params.in = last + 1;
333 	} else
334 	    a_params.in = (char *)file;
335 
336 	a_params.in_size  = strlen (a_params.in) + 1;
337 	a_params.out_size = maxsize;
338 
339 	ret = k_pioctl (last ? path_bkp : "." ,
340 			VIOC_AFS_STAT_MT_PT, &a_params, 0);
341 	free (a_params.out);
342 	if (ret < 0) {
343 	    free (path_bkp);
344 
345 	    if (errno != EINVAL)
346 		return ret;
347 	    else
348 		/* if we get EINVAL this is probably not a mountpoint */
349 		return lstat (file, sb);
350 	}
351 
352 	/*
353 	 * wow this was a mountpoint, lets cook the struct stat
354 	 * use . as a prototype
355 	 */
356 
357 	ret = lstat (path_bkp, sb);
358 	free (path_bkp);
359 	if (ret < 0)
360 	    return ret;
361 
362 	if (ino_last == sb->st_ino)
363 	    ino_counter++;
364 	else {
365 	    ino_last    = sb->st_ino;
366 	    ino_counter = 0;
367 	}
368 	sb->st_ino += ino_counter;
369 	sb->st_nlink = 3;
370 
371 	return 0;
372     }
373 #endif /* KRB4 */
374     return lstat (file, sb);
375 }
376 
377 static void
378 list_files(FILE *out, const char **files, int n_files, int flags)
379 {
380     struct fileinfo *fi;
381     int i;
382 
383     fi = calloc(n_files, sizeof(*fi));
384     if (fi == NULL) {
385 	sec_fprintf2(out, "ouf of memory\r\n");
386 	return;
387     }
388     for(i = 0; i < n_files; i++) {
389 	if(lstat_file(files[i], &fi[i].st) < 0) {
390 	    sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));
391 	    fi[i].filename = NULL;
392 	} else {
393 	    if((flags & LS_DIRS) == 0 && S_ISDIR(fi[i].st.st_mode)) {
394 		if(n_files > 1)
395 		    sec_fprintf2(out, "%s:\r\n", files[i]);
396 		list_dir(out, files[i], flags);
397 	    } else {
398 		make_fileinfo(files[i], &fi[i], flags);
399 	    }
400 	}
401     }
402     switch(SORT_MODE(flags)) {
403     case LS_SORT_NAME:
404 	qsort(fi, n_files, sizeof(*fi),
405 	      (int (*)(const void*, const void*))compare_filename);
406 	break;
407     case LS_SORT_MTIME:
408 	qsort(fi, n_files, sizeof(*fi),
409 	      (int (*)(const void*, const void*))compare_mtime);
410 	break;
411     case LS_SORT_SIZE:
412 	qsort(fi, n_files, sizeof(*fi),
413 	      (int (*)(const void*, const void*))compare_size);
414 	break;
415     }
416     {
417 	int max_inode = 0;
418 	int max_bsize = 0;
419 	int max_n_link = 0;
420 	int max_user = 0;
421 	int max_group = 0;
422 	int max_size = 0;
423 	int max_major = 0;
424 	int max_minor = 0;
425 	int max_date = 0;
426 	for(i = 0; i < n_files; i++) {
427 	    if(fi[i].filename == NULL)
428 		continue;
429 	    if(fi[i].inode > max_inode)
430 		max_inode = fi[i].inode;
431 	    if(fi[i].bsize > max_bsize)
432 		max_bsize = fi[i].bsize;
433 	    if(fi[i].n_link > max_n_link)
434 		max_n_link = fi[i].n_link;
435 	    if(strlen(fi[i].user) > max_user)
436 		max_user = strlen(fi[i].user);
437 	    if(strlen(fi[i].group) > max_group)
438 		max_group = strlen(fi[i].group);
439 	    if(fi[i].major != NULL && strlen(fi[i].major) > max_major)
440 		max_major = strlen(fi[i].major);
441 	    if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)
442 		max_minor = strlen(fi[i].minor);
443 	    if(fi[i].size != NULL && strlen(fi[i].size) > max_size)
444 		max_size = strlen(fi[i].size);
445 	    if(strlen(fi[i].date) > max_date)
446 		max_date = strlen(fi[i].date);
447 	}
448 	if(max_size < max_major + max_minor + 2)
449 	    max_size = max_major + max_minor + 2;
450 	else if(max_size - max_minor - 2 > max_major)
451 	    max_major = max_size - max_minor - 2;
452 	max_inode = log10(max_inode);
453 	max_bsize = log10(max_bsize);
454 	max_n_link = log10(max_n_link);
455 
456 	if(flags & LS_SORT_REVERSE)
457 	    for(i = n_files - 1; i >= 0; i--)
458 		print_file(out,
459 			   flags,
460 			   &fi[i],
461 			   max_inode,
462 			   max_bsize,
463 			   max_n_link,
464 			   max_user,
465 			   max_group,
466 			   max_size,
467 			   max_major,
468 			   max_minor,
469 			   max_date);
470 	else
471 	    for(i = 0; i < n_files; i++)
472 		print_file(out,
473 			   flags,
474 			   &fi[i],
475 			   max_inode,
476 			   max_bsize,
477 			   max_n_link,
478 			   max_user,
479 			   max_group,
480 			   max_size,
481 			   max_major,
482 			   max_minor,
483 			   max_date);
484 	for(i = 0; i < n_files; i++)
485 	    free_fileinfo(&fi[i]);
486 	free(fi);
487     }
488 }
489 
490 static void
491 free_files (char **files, int n)
492 {
493     int i;
494 
495     for (i = 0; i < n; ++i)
496 	free (files[i]);
497     free (files);
498 }
499 
500 static void
501 list_dir(FILE *out, const char *directory, int flags)
502 {
503     DIR *d = opendir(directory);
504     struct dirent *ent;
505     char **files = NULL;
506     int n_files = 0;
507 
508     if(d == NULL) {
509 	sec_fprintf2(out, "%s: %s\r\n", directory, strerror(errno));
510 	return;
511     }
512     while((ent = readdir(d)) != NULL) {
513 	void *tmp;
514 
515 	if(ent->d_name[0] == '.') {
516 	    if (flags & LS_IGNORE_DOT)
517 	        continue;
518 	    if (ent->d_name[1] == 0) /* Ignore . */
519 	        continue;
520 	    if (ent->d_name[1] == '.' && ent->d_name[2] == 0) /* Ignore .. */
521 	        continue;
522 	}
523 	tmp = realloc(files, (n_files + 1) * sizeof(*files));
524 	if (tmp == NULL) {
525 	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
526 	    free_files (files, n_files);
527 	    closedir (d);
528 	    return;
529 	}
530 	files = tmp;
531 	asprintf(&files[n_files], "%s/%s", directory, ent->d_name);
532 	if (files[n_files] == NULL) {
533 	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
534 	    free_files (files, n_files);
535 	    closedir (d);
536 	    return;
537 	}
538 	++n_files;
539     }
540     closedir(d);
541     list_files(out, (const char**)files, n_files, flags | LS_DIRS);
542 }
543 
544 void
545 builtin_ls(FILE *out, const char *file)
546 {
547     int flags = LS_SORT_NAME;
548 
549     if(*file == '-') {
550 	const char *p;
551 	for(p = file + 1; *p; p++) {
552 	    switch(*p) {
553 	    case 'a':
554 	    case 'A':
555 		flags &= ~LS_IGNORE_DOT;
556 		break;
557 	    case 'C':
558 		break;
559 	    case 'd':
560 		flags |= LS_DIRS;
561 		break;
562 	    case 'f':
563 		flags = (flags & ~LS_SORT_MODE);
564 		break;
565 	    case 'i':
566 		flags |= flags | LS_INODE;
567 		break;
568 	    case 'l':
569 		break;
570 	    case 't':
571 		flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
572 		break;
573 	    case 's':
574 		flags |= LS_SIZE;
575 		break;
576 	    case 'S':
577 		flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
578 		break;
579 	    case 'r':
580 		flags |= LS_SORT_REVERSE;
581 		break;
582 	    }
583 	}
584 	file = ".";
585     }
586     list_files(out, &file, 1, flags);
587     sec_fflush(out);
588 }
589