/* * Copyright (c) 1999 - 2002 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$"); #else #include #include #include #include #include #include #include #include #include #include #define sec_fprintf2 fprintf #define sec_fflush fflush static void list_files(FILE *out, const char **files, int n_files, int flags); static int parse_flags(const char *options); int main(int argc, char **argv) { int i = 1; int flags; if(argc > 1 && argv[1][0] == '-') { flags = parse_flags(argv[1]); i = 2; } else flags = parse_flags(NULL); list_files(stdout, (const char **)argv + i, argc - i, flags); 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) #define LS_SHOW_ALL (1 << 10) #define LS_RECURSIVE (1 << 11) #define LS_EXTRA_BLANK (1 << 12) #define LS_SHOW_DIRNAME (1 << 13) #define LS_DIR_FLAG (1 << 14) /* these files come via list_dir */ #ifndef S_ISTXT #define S_ISTXT S_ISVTX #endif #if !defined(_S_IFMT) && defined(S_IFMT) #define _S_IFMT S_IFMT #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 size_t block_convert(size_t blocks) { #ifdef S_BLKSIZE return blocks * S_BLKSIZE / 1024; #else return blocks * 512 / 1024; #endif } static int make_fileinfo(FILE *out, 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; file->bsize = block_convert(st->st_blocks); 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) { if (asprintf(&file->user, "%u", (unsigned)st->st_uid) == -1) file->user = NULL; } else file->user = strdup(pwd->pw_name); if (file->user == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } { struct group *grp; grp = getgrgid(st->st_gid); if(grp == NULL) { if (asprintf(&file->group, "%u", (unsigned)st->st_gid) == -1) file->group = NULL; } else file->group = strdup(grp->gr_name); if (file->group == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) { #if defined(major) && defined(minor) if (asprintf(&file->major, "%u", (unsigned)major(st->st_rdev)) == -1) file->major = NULL; if (asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev)) == -1) file->minor = NULL; #else /* Don't want to use the DDI/DKI crap. */ if (asprintf(&file->major, "%u", (unsigned)st->st_rdev) == -1) file->major = NULL; if (asprintf(&file->minor, "%u", 0) == -1) file->minor = NULL; #endif if (file->major == NULL || file->minor == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } else { if (asprintf(&file->size, "%lu", (unsigned long)st->st_size) == -1) file->size = NULL; } { 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); if (file->date == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } { const char *p = strrchr(filename, '/'); if(p) p++; else p = filename; if((flags & LS_TYPE) && file_type != 0) { if (asprintf(&file->filename, "%s%c", p, file_type) == -1) file->filename = NULL; } else file->filename = strdup(p); if (file->filename == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } if(S_ISLNK(st->st_mode)) { int n; n = readlink((char *)filename, buf, sizeof(buf) - 1); if(n >= 0) { buf[n] = '\0'; file->link = strdup(buf); if (file->link == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } } else sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno)); } return 0; } 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 int list_dir(FILE*, const char*, int); static int find_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 KRB5 static int do_the_afs_dance = 1; #endif static int lstat_file (const char *file, struct stat *sb) { #ifdef KRB5 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 /* KRB5 */ return lstat (file, sb); } #define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \ ((X)[1] == '.' && (X)[2] == '\0'))) static int list_files(FILE *out, const char **files, int n_files, int flags) { struct fileinfo *fi; int i; int *dirs = NULL; size_t total_blocks = 0; int n_print = 0; int ret = 0; if(n_files == 0) return 0; if(n_files > 1) flags |= LS_SHOW_DIRNAME; fi = calloc(n_files, sizeof(*fi)); if (fi == NULL) { syslog(LOG_ERR, "out of memory"); return -1; } 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 { int include_in_list = 1; total_blocks += block_convert(fi[i].st.st_blocks); if(S_ISDIR(fi[i].st.st_mode)) { if(dirs == NULL) dirs = calloc(n_files, sizeof(*dirs)); if(dirs == NULL) { syslog(LOG_ERR, "%s: %m", files[i]); ret = -1; goto out; } dirs[i] = 1; if((flags & LS_DIRS) == 0) include_in_list = 0; } if(include_in_list) { ret = make_fileinfo(out, files[i], &fi[i], flags); if (ret) goto out; n_print++; } } } 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 = find_log10(max_inode); max_bsize = find_log10(max_bsize); max_n_link = find_log10(max_n_link); if(n_print > 0) sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks); 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 size_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); if(find_log10(fi[i].bsize) > size_len) size_len = find_log10(fi[i].bsize); } if(num_files == 0) goto next; if(flags & LS_SIZE) { columns = 80 / (size_len + 1 + max_len + 1); max_len = 80 / columns - size_len - 1; } else { columns = 80 / (max_len + 1); /* get space between columns */ max_len = 80 / columns; } if(flags & LS_SIZE) sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks); if(DISP_MODE(flags) == LS_DISP_CROSS) { for(i = 0, j = 0; i < n_files; i++) { if(fi[i].filename == NULL) continue; if(flags & LS_SIZE) sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize, max_len, fi[i].filename); else 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; for(i = 0; i < skip; i++) { for(j = i; j < n_files;) { while(j < n_files && fi[j].filename == NULL) j++; if(flags & LS_SIZE) sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize, max_len, fi[j].filename); else 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); } } next: if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) { for(i = 0; i < n_files; i++) { if(dirs[i]) { const char *p = strrchr(files[i], '/'); if(p == NULL) p = files[i]; else p++; if(!(flags & LS_DIR_FLAG) || !IS_DOT_DOTDOT(p)) { if((flags & LS_SHOW_DIRNAME)) { if ((flags & LS_EXTRA_BLANK)) sec_fprintf2(out, "\r\n"); sec_fprintf2(out, "%s:\r\n", files[i]); } list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK); } } } } out: for(i = 0; i < n_files; i++) free_fileinfo(&fi[i]); free(fi); if(dirs != NULL) free(dirs); return ret; } static void free_files (char **files, int n) { int i; for (i = 0; i < n; ++i) free (files[i]); free (files); } static int hide_file(const char *filename, int flags) { if(filename[0] != '.') return 0; if((flags & LS_IGNORE_DOT)) return 1; if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) { if((flags & LS_SHOW_ALL)) return 0; else return 1; } return 0; } static int list_dir(FILE *out, const char *directory, int flags) { DIR *d = opendir(directory); struct dirent *ent; char **files = NULL; int n_files = 0; int ret; if(d == NULL) { syslog(LOG_ERR, "%s: %m", directory); return -1; } while((ent = readdir(d)) != NULL) { void *tmp; if(hide_file(ent->d_name, flags)) continue; tmp = realloc(files, (n_files + 1) * sizeof(*files)); if (tmp == NULL) { syslog(LOG_ERR, "%s: out of memory", directory); free_files (files, n_files); closedir (d); return -1; } files = tmp; ret = asprintf(&files[n_files], "%s/%s", directory, ent->d_name); if (ret == -1) { syslog(LOG_ERR, "%s: out of memory", directory); free_files (files, n_files); closedir (d); return -1; } ++n_files; } closedir(d); return list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG); } static int parse_flags(const char *options) { #ifdef TEST int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN; #else int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG; #endif const char *p; if(options == NULL || *options != '-') return flags; for(p = options + 1; *p; p++) { switch(*p) { case '1': flags = (flags & ~LS_DISP_MODE); break; case 'a': flags |= LS_SHOW_ALL; /*FALLTHROUGH*/ 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 'r': flags |= LS_SORT_REVERSE; break; case 'R': flags |= LS_RECURSIVE; break; case 's': flags |= LS_SIZE; break; case 'S': flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE; break; case 't': flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME; break; case 'x': flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS; break; /* these are a bunch of unimplemented flags from BSD ls */ case 'k': /* display sizes in kB */ case 'c': /* last change time */ case 'L': /* list symlink target */ case 'm': /* stream output */ case 'o': /* BSD file flags */ case 'p': /* display / after directories */ case 'q': /* print non-graphic characters */ case 'u': /* use last access time */ case 'T': /* display complete time */ case 'W': /* include whiteouts */ break; } } return flags; } int builtin_ls(FILE *out, const char *file) { int flags; int ret; if(*file == '-') { flags = parse_flags(file); file = "."; } else flags = parse_flags(""); ret = list_files(out, &file, 1, flags); sec_fflush(out); return ret; }