/* vi: set sw=4 ts=4: * * ls.c - list files * * Copyright 2012 Andre Renaud * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html USE_LS(NEWTOY(ls, "AnRlF1a", TOYFLAG_BIN)) config LS bool "ls" default n help usage: ls [-lFaA1] [directory...] list files -1 list one file per line -a list all files -A list all files except . and .. -F append a character as a file type indicator -l show full details for each file */ /* So that we can do 64-bit stat etc... */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include "toys.h" #define FLAG_a 1 #define FLAG_1 2 #define FLAG_F 4 #define FLAG_l 8 #define FLAG_R 16 #define FLAG_n 32 #define FLAG_A 64 static int dir_filter(const struct dirent *d) { /* Skip over all '.*' entries, unless -a is given */ if (!(toys.optflags & FLAG_a)) { /* -A means show everything except the . & .. entries */ if (toys.optflags & FLAG_A) { if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) return 0; } else if (d->d_name[0] == '.') return 0; } return 1; } static void do_ls(int fd, char *name) { struct dirent **entries; int nentries; int i; int maxwidth = -1; int ncolumns = 1; struct dirent file_dirent; struct dirent *file_direntp; if (!name || strcmp(name, "-") == 0) name = "."; if (toys.optflags & FLAG_R) xprintf("\n%s:\n", name); /* Get all the files in this directory */ nentries = scandir(name, &entries, dir_filter, alphasort); if (nentries < 0) { /* We've just selected a single file, so create a single-length list */ /* FIXME: This means that ls *.x results in a whole bunch of single * listings, not one combined listing. */ if (errno == ENOTDIR) { nentries = 1; strcpy(file_dirent.d_name, name); file_direntp = &file_dirent; entries = &file_direntp; } else perror_exit("ls: cannot access %s'", name); } /* Determine the widest entry so we can flow them properly */ if (!(toys.optflags & FLAG_1)) { int columns; char *columns_str; for (i = 0; i < nentries; i++) { struct dirent *ent = entries[i]; int width; width = strlen(ent->d_name); if (width > maxwidth) maxwidth = width; } /* We always want at least a single space for each entry */ maxwidth++; if (toys.optflags & FLAG_F) maxwidth++; columns_str = getenv("COLUMNS"); columns = columns_str ? atoi(columns_str) : 80; ncolumns = maxwidth ? columns / maxwidth : 1; } for (i = 0; i < nentries; i++) { struct dirent *ent = entries[i]; int len = strlen(ent->d_name); struct stat st; int stat_valid = 0; sprintf(toybuf, "%s/%s", name, ent->d_name); /* Provide the ls -l long output */ if (toys.optflags & FLAG_l) { char type; char timestamp[64]; struct tm mtime; if (lstat(toybuf, &st)) perror_exit("Can't stat %s", toybuf); stat_valid = 1; if (S_ISDIR(st.st_mode)) type = 'd'; else if (S_ISCHR(st.st_mode)) type = 'c'; else if (S_ISBLK(st.st_mode)) type = 'b'; else if (S_ISLNK(st.st_mode)) type = 'l'; else type = '-'; xprintf("%c%c%c%c%c%c%c%c%c%c ", type, (st.st_mode & S_IRUSR) ? 'r' : '-', (st.st_mode & S_IWUSR) ? 'w' : '-', (st.st_mode & S_IXUSR) ? 'x' : '-', (st.st_mode & S_IRGRP) ? 'r' : '-', (st.st_mode & S_IWGRP) ? 'w' : '-', (st.st_mode & S_IXGRP) ? 'x' : '-', (st.st_mode & S_IROTH) ? 'r' : '-', (st.st_mode & S_IWOTH) ? 'w' : '-', (st.st_mode & S_IXOTH) ? 'x' : '-'); xprintf("%2d ", st.st_nlink); if (toys.optflags & FLAG_n) { xprintf("%4d ", st.st_uid); xprintf("%4d ", st.st_gid); } else { struct passwd *pwd = getpwuid(st.st_uid); struct group *grp = getgrgid(st.st_gid); if (!pwd) xprintf("%4d ", st.st_uid); else xprintf("%-10s ", pwd->pw_name); if (!grp) xprintf("%4d ", st.st_gid); else xprintf("%-10s ", grp->gr_name); } if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) xprintf("%3d, %3d ", major(st.st_rdev), minor(st.st_rdev)); else xprintf("%12lld ", st.st_size); localtime_r(&st.st_mtime, &mtime); strftime(timestamp, sizeof(timestamp), "%b %e %H:%M", &mtime); xprintf("%s ", timestamp); } xprintf("%s", ent->d_name); /* Append the file-type indicator character */ if (toys.optflags & FLAG_F) { if (!stat_valid) { if (lstat(toybuf, &st)) perror_exit("Can't stat %s", toybuf); stat_valid = 1; } if (S_ISDIR(st.st_mode)) { xprintf("/"); len++; } else if (S_ISREG(st.st_mode) && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { xprintf("*"); len++; } else if (S_ISLNK(st.st_mode)) { xprintf("@"); len++; } } if (toys.optflags & FLAG_1) { xprintf("\n"); } else { if (i % ncolumns == ncolumns - 1) xprintf("\n"); else xprintf("%*s", maxwidth - len, ""); } } /* Make sure we put at a trailing new line in */ if (!(toys.optflags & FLAG_1) && (i % ncolumns)) xprintf("\n"); if (toys.optflags & FLAG_R) { for (i = 0; i < nentries; i++) { struct dirent *ent = entries[i]; struct stat st; char dirname[PATH_MAX]; sprintf(dirname, "%s/%s", name, ent->d_name); if (lstat(dirname, &st)) perror_exit("Can't stat %s", dirname); if (S_ISDIR(st.st_mode)) do_ls(0, dirname); } } } void ls_main(void) { /* If the output is not a TTY, then just do one-file per line * This makes ls easier to use with other command line tools (grep/awk etc...) */ if (!isatty(fileno(stdout))) toys.optflags |= FLAG_1; /* Long output must be one-file per line */ if (toys.optflags & FLAG_l) toys.optflags |= FLAG_1; loopfiles(toys.optargs, do_ls); }