/* * Copyright (c) 1999 - 2000 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of KTH nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef TEST #include "ftpd_locl.h" RCSID("$Id: ls.c,v 1.20 2001/01/25 01:33:15 joda Exp $"); #else #include #include #include #include #include #include #include #include #include #include #define sec_fprintf2 fprintf #define sec_fflush fflush void builtin_ls(FILE *out, const char *file); int main(int argc, char **argv) { int i; for(i = 1; i < argc; i++) builtin_ls(stdout, argv[i]); return 0; } #endif struct fileinfo { struct stat st; int inode; int bsize; char mode[11]; int n_link; char *user; char *group; char *size; char *major; char *minor; char *date; char *filename; char *link; }; static void free_fileinfo(struct fileinfo *f) { free(f->user); free(f->group); free(f->size); free(f->major); free(f->minor); free(f->date); free(f->filename); free(f->link); } #define LS_DIRS (1 << 0) #define LS_IGNORE_DOT (1 << 1) #define LS_SORT_MODE (3 << 2) #define SORT_MODE(f) ((f) & LS_SORT_MODE) #define LS_SORT_NAME (1 << 2) #define LS_SORT_MTIME (2 << 2) #define LS_SORT_SIZE (3 << 2) #define LS_SORT_REVERSE (1 << 4) #define LS_SIZE (1 << 5) #define LS_INODE (1 << 6) #define LS_TYPE (1 << 7) #define LS_DISP_MODE (3 << 8) #define DISP_MODE(f) ((f) & LS_DISP_MODE) #define LS_DISP_LONG (1 << 8) #define LS_DISP_COLUMN (2 << 8) #define LS_DISP_CROSS (3 << 8) #ifndef S_ISTXT #define S_ISTXT S_ISVTX #endif #ifndef S_ISSOCK #define S_ISSOCK(mode) (((mode) & _S_IFMT) == S_IFSOCK) #endif #ifndef S_ISLNK #define S_ISLNK(mode) (((mode) & _S_IFMT) == S_IFLNK) #endif static void make_fileinfo(const char *filename, struct fileinfo *file, int flags) { char buf[128]; int file_type = 0; struct stat *st = &file->st; file->inode = st->st_ino; #ifdef S_BLKSIZE file->bsize = st->st_blocks * S_BLKSIZE / 1024; #else file->bsize = st->st_blocks * 512 / 1024; #endif if(S_ISDIR(st->st_mode)) { file->mode[0] = 'd'; file_type = '/'; } else if(S_ISCHR(st->st_mode)) file->mode[0] = 'c'; else if(S_ISBLK(st->st_mode)) file->mode[0] = 'b'; else if(S_ISREG(st->st_mode)) { file->mode[0] = '-'; if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) file_type = '*'; } else if(S_ISFIFO(st->st_mode)) { file->mode[0] = 'p'; file_type = '|'; } else if(S_ISLNK(st->st_mode)) { file->mode[0] = 'l'; file_type = '@'; } else if(S_ISSOCK(st->st_mode)) { file->mode[0] = 's'; file_type = '='; } #ifdef S_ISWHT else if(S_ISWHT(st->st_mode)) { file->mode[0] = 'w'; file_type = '%'; } #endif else file->mode[0] = '?'; { char *x[] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" }; strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]); strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]); strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]); if((st->st_mode & S_ISUID)) { if((st->st_mode & S_IXUSR)) file->mode[3] = 's'; else file->mode[3] = 'S'; } if((st->st_mode & S_ISGID)) { if((st->st_mode & S_IXGRP)) file->mode[6] = 's'; else file->mode[6] = 'S'; } if((st->st_mode & S_ISTXT)) { if((st->st_mode & S_IXOTH)) file->mode[9] = 't'; else file->mode[9] = 'T'; } } file->n_link = st->st_nlink; { struct passwd *pwd; pwd = getpwuid(st->st_uid); if(pwd == NULL) asprintf(&file->user, "%u", (unsigned)st->st_uid); else file->user = strdup(pwd->pw_name); } { struct group *grp; grp = getgrgid(st->st_gid); if(grp == NULL) asprintf(&file->group, "%u", (unsigned)st->st_gid); else file->group = strdup(grp->gr_name); } if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) { #if defined(major) && defined(minor) asprintf(&file->major, "%u", (unsigned)major(st->st_rdev)); asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev)); #else /* Don't want to use the DDI/DKI crap. */ asprintf(&file->major, "%u", (unsigned)st->st_rdev); asprintf(&file->minor, "%u", 0); #endif } else asprintf(&file->size, "%lu", (unsigned long)st->st_size); { time_t t = time(NULL); time_t mtime = st->st_mtime; struct tm *tm = localtime(&mtime); if((t - mtime > 6*30*24*60*60) || (mtime - t > 6*30*24*60*60)) strftime(buf, sizeof(buf), "%b %e %Y", tm); else strftime(buf, sizeof(buf), "%b %e %H:%M", tm); file->date = strdup(buf); } { const char *p = strrchr(filename, '/'); if(p) p++; else p = filename; if((flags & LS_TYPE) && file_type != 0) asprintf(&file->filename, "%s%c", p, file_type); else file->filename = strdup(p); } if(S_ISLNK(st->st_mode)) { int n; n = readlink((char *)filename, buf, sizeof(buf)); if(n >= 0) { buf[n] = '\0'; file->link = strdup(buf); } else warn("%s: readlink", filename); } } static void print_file(FILE *out, int flags, struct fileinfo *f, int max_inode, int max_bsize, int max_n_link, int max_user, int max_group, int max_size, int max_major, int max_minor, int max_date) { if(f->filename == NULL) return; if(flags & LS_INODE) { sec_fprintf2(out, "%*d", max_inode, f->inode); sec_fprintf2(out, " "); } if(flags & LS_SIZE) { sec_fprintf2(out, "%*d", max_bsize, f->bsize); sec_fprintf2(out, " "); } sec_fprintf2(out, "%s", f->mode); sec_fprintf2(out, " "); sec_fprintf2(out, "%*d", max_n_link, f->n_link); sec_fprintf2(out, " "); sec_fprintf2(out, "%-*s", max_user, f->user); sec_fprintf2(out, " "); sec_fprintf2(out, "%-*s", max_group, f->group); sec_fprintf2(out, " "); if(f->major != NULL && f->minor != NULL) sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor); else sec_fprintf2(out, "%*s", max_size, f->size); sec_fprintf2(out, " "); sec_fprintf2(out, "%*s", max_date, f->date); sec_fprintf2(out, " "); sec_fprintf2(out, "%s", f->filename); if(f->link) sec_fprintf2(out, " -> %s", f->link); sec_fprintf2(out, "\r\n"); } static int compare_filename(struct fileinfo *a, struct fileinfo *b) { if(a->filename == NULL) return 1; if(b->filename == NULL) return -1; return strcmp(a->filename, b->filename); } static int compare_mtime(struct fileinfo *a, struct fileinfo *b) { if(a->filename == NULL) return 1; if(b->filename == NULL) return -1; return b->st.st_mtime - a->st.st_mtime; } static int compare_size(struct fileinfo *a, struct fileinfo *b) { if(a->filename == NULL) return 1; if(b->filename == NULL) return -1; return b->st.st_size - a->st.st_size; } static void list_dir(FILE *out, const char *directory, int flags); static int log10(int num) { int i = 1; while(num > 10) { i++; num /= 10; } return i; } /* * Operate as lstat but fake up entries for AFS mount points so we don't * have to fetch them. */ #ifdef KRB4 static int do_the_afs_dance = 1; #endif static int lstat_file (const char *file, struct stat *sb) { #ifdef KRB4 if (do_the_afs_dance && k_hasafs() && strcmp(file, ".") && strcmp(file, "..") && strcmp(file, "/")) { struct ViceIoctl a_params; char *dir, *last; char *path_bkp; static ino_t ino_counter = 0, ino_last = 0; int ret; const int maxsize = 2048; path_bkp = strdup (file); if (path_bkp == NULL) return -1; a_params.out = malloc (maxsize); if (a_params.out == NULL) { free (path_bkp); return -1; } /* If path contains more than the filename alone - split it */ last = strrchr (path_bkp, '/'); if (last != NULL) { if(last[1] == '\0') /* if path ended in /, replace with `.' */ a_params.in = "."; else a_params.in = last + 1; while(last > path_bkp && *--last == '/'); if(*last != '/' || last != path_bkp) { *++last = '\0'; dir = path_bkp; } else /* we got to the start, so this must be the root dir */ dir = "/"; } else { /* file is relative to cdir */ dir = "."; a_params.in = path_bkp; } a_params.in_size = strlen (a_params.in) + 1; a_params.out_size = maxsize; ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0); free (a_params.out); if (ret < 0) { free (path_bkp); if (errno != EINVAL) return ret; else /* if we get EINVAL this is probably not a mountpoint */ return lstat (file, sb); } /* * wow this was a mountpoint, lets cook the struct stat * use . as a prototype */ ret = lstat (dir, sb); free (path_bkp); if (ret < 0) return ret; if (ino_last == sb->st_ino) ino_counter++; else { ino_last = sb->st_ino; ino_counter = 0; } sb->st_ino += ino_counter; sb->st_nlink = 3; return 0; } #endif /* KRB4 */ return lstat (file, sb); } static void list_files(FILE *out, const char **files, int n_files, int flags) { struct fileinfo *fi; int i; fi = calloc(n_files, sizeof(*fi)); if (fi == NULL) { sec_fprintf2(out, "ouf of memory\r\n"); return; } for(i = 0; i < n_files; i++) { if(lstat_file(files[i], &fi[i].st) < 0) { sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno)); fi[i].filename = NULL; } else { if((flags & LS_DIRS) == 0 && S_ISDIR(fi[i].st.st_mode)) { if(n_files > 1) sec_fprintf2(out, "%s:\r\n", files[i]); list_dir(out, files[i], flags); } else { make_fileinfo(files[i], &fi[i], flags); } } } switch(SORT_MODE(flags)) { case LS_SORT_NAME: qsort(fi, n_files, sizeof(*fi), (int (*)(const void*, const void*))compare_filename); break; case LS_SORT_MTIME: qsort(fi, n_files, sizeof(*fi), (int (*)(const void*, const void*))compare_mtime); break; case LS_SORT_SIZE: qsort(fi, n_files, sizeof(*fi), (int (*)(const void*, const void*))compare_size); break; } if(DISP_MODE(flags) == LS_DISP_LONG) { int max_inode = 0; int max_bsize = 0; int max_n_link = 0; int max_user = 0; int max_group = 0; int max_size = 0; int max_major = 0; int max_minor = 0; int max_date = 0; for(i = 0; i < n_files; i++) { if(fi[i].filename == NULL) continue; if(fi[i].inode > max_inode) max_inode = fi[i].inode; if(fi[i].bsize > max_bsize) max_bsize = fi[i].bsize; if(fi[i].n_link > max_n_link) max_n_link = fi[i].n_link; if(strlen(fi[i].user) > max_user) max_user = strlen(fi[i].user); if(strlen(fi[i].group) > max_group) max_group = strlen(fi[i].group); if(fi[i].major != NULL && strlen(fi[i].major) > max_major) max_major = strlen(fi[i].major); if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor) max_minor = strlen(fi[i].minor); if(fi[i].size != NULL && strlen(fi[i].size) > max_size) max_size = strlen(fi[i].size); if(strlen(fi[i].date) > max_date) max_date = strlen(fi[i].date); } if(max_size < max_major + max_minor + 2) max_size = max_major + max_minor + 2; else if(max_size - max_minor - 2 > max_major) max_major = max_size - max_minor - 2; max_inode = log10(max_inode); max_bsize = log10(max_bsize); max_n_link = log10(max_n_link); if(flags & LS_SORT_REVERSE) for(i = n_files - 1; i >= 0; i--) print_file(out, flags, &fi[i], max_inode, max_bsize, max_n_link, max_user, max_group, max_size, max_major, max_minor, max_date); else for(i = 0; i < n_files; i++) print_file(out, flags, &fi[i], max_inode, max_bsize, max_n_link, max_user, max_group, max_size, max_major, max_minor, max_date); } else if(DISP_MODE(flags) == LS_DISP_COLUMN || DISP_MODE(flags) == LS_DISP_CROSS) { int max_len = 0; int num_files = n_files; int columns; int j; for(i = 0; i < n_files; i++) { if(fi[i].filename == NULL) { num_files--; continue; } if(strlen(fi[i].filename) > max_len) max_len = strlen(fi[i].filename); } columns = 80 / (max_len + 1); /* get space between columns */ max_len = 80 / columns; if(DISP_MODE(flags) == LS_DISP_CROSS) { for(i = 0, j = 0; i < n_files; i++) { if(fi[i].filename == NULL) continue; sec_fprintf2(out, "%-*s", max_len, fi[i].filename); j++; if(j == columns) { sec_fprintf2(out, "\r\n"); j = 0; } } if(j > 0) sec_fprintf2(out, "\r\n"); } else { int skip = (num_files + columns - 1) / columns; j = 0; for(i = 0; i < skip; i++) { for(j = i; j < n_files;) { while(j < n_files && fi[j].filename == NULL) j++; sec_fprintf2(out, "%-*s", max_len, fi[j].filename); j += skip; } sec_fprintf2(out, "\r\n"); } } } else { for(i = 0; i < n_files; i++) { if(fi[i].filename == NULL) continue; sec_fprintf2(out, "%s\r\n", fi[i].filename); } } for(i = 0; i < n_files; i++) free_fileinfo(&fi[i]); free(fi); } static void free_files (char **files, int n) { int i; for (i = 0; i < n; ++i) free (files[i]); free (files); } static void list_dir(FILE *out, const char *directory, int flags) { DIR *d = opendir(directory); struct dirent *ent; char **files = NULL; int n_files = 0; if(d == NULL) { sec_fprintf2(out, "%s: %s\r\n", directory, strerror(errno)); return; } while((ent = readdir(d)) != NULL) { void *tmp; if(ent->d_name[0] == '.') { if (flags & LS_IGNORE_DOT) continue; if (ent->d_name[1] == 0) /* Ignore . */ continue; if (ent->d_name[1] == '.' && ent->d_name[2] == 0) /* Ignore .. */ continue; } tmp = realloc(files, (n_files + 1) * sizeof(*files)); if (tmp == NULL) { sec_fprintf2(out, "%s: out of memory\r\n", directory); free_files (files, n_files); closedir (d); return; } files = tmp; asprintf(&files[n_files], "%s/%s", directory, ent->d_name); if (files[n_files] == NULL) { sec_fprintf2(out, "%s: out of memory\r\n", directory); free_files (files, n_files); closedir (d); return; } ++n_files; } closedir(d); list_files(out, (const char**)files, n_files, flags | LS_DIRS); } void builtin_ls(FILE *out, const char *file) { int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG; if(*file == '-') { const char *p; for(p = file + 1; *p; p++) { switch(*p) { case '1': flags = (flags & ~LS_DISP_MODE); break; case 'a': case 'A': flags &= ~LS_IGNORE_DOT; break; case 'C': flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN; break; case 'd': flags |= LS_DIRS; break; case 'f': flags = (flags & ~LS_SORT_MODE); break; case 'F': flags |= LS_TYPE; break; case 'i': flags |= LS_INODE; break; case 'l': flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG; break; case 't': flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME; break; case 's': flags |= LS_SIZE; break; case 'S': flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE; break; case 'r': flags |= LS_SORT_REVERSE; break; case 'x': flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS; break; } } file = "."; } list_files(out, &file, 1, flags); sec_fflush(out); }