That's no tree. This is a tree (written long long ago):
tree.c
/**
* Print a directory `tree'.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <iconv.h>
#include <langinfo.h> /* nl_langinfo() */
#include <limits.h> /* PATH_MAX */
#include <locale.h> /* setlocale() */
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* getopt() */
/* Declarations */
struct entinfo { /* describes a directory entry */
char name[MAXNAMLEN + 1]; /* entry-name */
char dname[MAXNAMLEN + 1]; /* converted name for display */
unsigned type; /* entry type */
bool islast; /* is last entry in a directory */
};
/* Functions */
static bool getents(const char*, struct entinfo***, size_t*);
static bool isdir(const struct entinfo*);
static bool isdotdir(const char*);
static char* concat(const char*, const char*);
static int dircmp(const void*, const void*);
static int do_opts(const int, char*[]);
static int walk(const char*, const int, bool*);
static struct entinfo* direntcpy(const struct dirent*);
static void conv(iconv_t, char*, char* );
static void error(const char*, ...);
static void inittchars(iconv_t);
static void freeents(struct entinfo**, const size_t);
static void prtree(const struct entinfo*, const int, const bool*);
static void setlastdent(struct entinfo**, const size_t);
static void setlastfent(struct entinfo**, const size_t);
static void usage(FILE* fp);
static void* Realloc(void*, const size_t) __attribute__((returns_nonnull));
/* Globals */
static char* prog; /* program name */
static struct options { /* program options */
bool dodot; /* show entries starting with `.' */
bool dofile; /* show files in addition to dirs. */
bool dosort; /* sort directory entries */
bool dovt; /* use VT100 graph. chars. for tree */
} opts;
static const char* tree_vbar; /* string for vertical bar */
static const char* tree_norm; /* string for normal entries */
static const char* tree_last; /* string for last entry in dir. */
static iconv_t cd;
/**
* M A I N
*/
int
main(int argc, char* argv[])
{
bool branch[PATH_MAX]; /* print vert. bar in level if true */
int idx, rc = EXIT_FAILURE;
char* path, *cs;
setlocale(LC_ALL, "");
cs = nl_langinfo(CODESET);
if ((cd = iconv_open(cs, "UTF-8")) == (iconv_t)-1) {
error("%s: %s: iconv_open failed.", prog, cs);
exit(rc);
}
prog = argv[0];
memset(branch, 0, sizeof branch);
idx = do_opts(argc, argv);
argc -= idx;
argv += idx;
if (argc == 0 || **argv == '\0') {
path = Realloc(NULL, 2);
strcpy(path, ".");
} else if ((path = realpath(*argv, NULL)) == NULL) {
error("%s: %s: realpath failed.", prog, *argv);
exit(rc);
}
inittchars(cd); /* setup strings used for tree printing */
printf("%s:\n", path);
rc = walk(path, 0, branch);
free(path);
iconv_close(cd);
return rc;
}
/**
* Setup the strings used for tree printing.
*/
static void
inittchars(iconv_t ic)
{
if (opts.dovt) { /* use VT100 graph. chars. for tree */
tree_vbar = "\033(0x\033(B";
tree_norm = "\033(0tq\033(B";
tree_last = "\033(0mq\033(B";
} else { /* use ASCII or UTF-8 chars.? */
tree_vbar = "\xe2\x94\x83";
tree_norm = "\xe2\x94\xa0\xe2\x94\x81";
tree_last = "\xe2\x94\x97\xe2\x94\x81";
char buf[16];
char* s = tree_last, *d = buf;
size_t slen = strlen(s), dlen = sizeof buf;
if (iconv(ic, &s, &slen, &d, &dlen) != 0) { /* ASCII */
tree_vbar = "|";
tree_norm = "|-";
tree_last = "`-";
}
}
}
/**
* Recursively walk a directory tree starting at path.
* `level' is the current depth in the tree.
*/
static int
walk(const char* path, const int level, bool* branch)
{
struct entinfo** eip; /* entries in current directory */
size_t neip; /* no. of entries in array `eip' */
size_t i;
/*
* Get list of entries in specified directory.
*/
if (!getents(path, &eip, &neip))
return EXIT_FAILURE;
/*
* Sort the entries in lexicographic order.
*/
if (opts.dosort && neip > 0)
qsort(eip, neip, sizeof(struct entinfo* ), dircmp);
/*
* Mark the last entry, or the last directory entry
* in the array after sorting.
*/
opts.dofile ? setlastfent(eip, neip) : setlastdent(eip, neip);
for (i = 0; i < neip; i++) {
/*
* If entry is a directory...
*/
if (isdir(eip[i])) {
char* newpath;
/*
* If this is the last entry in a directory, then
* no more branches are possible, and we don't
* print any branch chars. for this level anymore.
*/
if (eip[i]->islast)
branch[level] = false; /* no branch */
else
branch[level] = true; /* print branch */
prtree(eip[i], level, branch);
/* recurse */
newpath = concat(path, eip[i]->name);
walk(newpath, level + 1, branch);
free(newpath);
}
/*
* Entry is a file. Files don't change the
* structure of a directory, so there is no
* change in the printing of branches.
*/
else if (opts.dofile)
prtree(eip[i], level, branch);
}
freeents(eip, neip); /* free entries and array */
return EXIT_SUCCESS;
}
/**
* Construct an array containing the entries in a directory.
* Store array and return the no. of entries read.
*/
static bool
getents(const char* path, struct entinfo*** eip, size_t* neip)
{
DIR* dir; /* current directory */
struct entinfo** ents; /* entries in current directory */
struct dirent* dent; /* directory entry */
size_t entsz; /* size of array `ents' */
size_t nent; /* no. of entries actually in `ents' */
dir = opendir(path);
if (dir == NULL) {
error("%s: %s: can't read directory.", prog, path);
*eip = NULL; /* no data */
*neip = 0; /* no entries */
return false;
}
nent = entsz = 0;
ents = NULL;
while ((dent = readdir(dir)) != NULL) {
/*
* Grow array if needed.
*/
if (nent == entsz) {
entsz = (entsz == 0) ? 64 : entsz * 2;
ents = Realloc(ents, entsz * sizeof(struct entinfo* ));
}
/*
* Store entries in array except `.' or `..'.
*/
if (!isdotdir(dent->d_name)) {
if (*dent->d_name == '.' && !opts.dodot)
continue; /* skip `.'-entries */
struct entinfo* e;
if ((e = direntcpy(dent)) != NULL)
ents[nent++] = e;
}
}
closedir(dir);
*eip = ents;
*neip = nent;
return true;
}
/**
* Mark the last entry in the array `eip'.
*/
static void
setlastfent(struct entinfo** eip, const size_t neip)
{
if (neip == 0)
return;
eip[neip - 1]->islast = true;
}
/**
* Mark the last _directory_ entry in the array `eip'.
*/
static void
setlastdent(struct entinfo** eip, const size_t neip)
{
size_t i, lastdir;
if (neip == 0)
return;
lastdir = neip - 1; /* last entry in any case */
for (i = 0; i < neip; i++)
if (isdir(eip[i]))
lastdir = i;
eip[lastdir]->islast = true;
}
/**
* Print tree branches for directory entry `eip'
* at depth `level'.
*
* The last entry in a directory is printed differently.
*/
static void
prtree(const struct entinfo* eip, const int level, const bool* branch)
{
int i;
printf(" ");
for (i = 0; i < level; i++)
printf("%s ", branch[i] ? tree_vbar : " ");
printf("%s %s\n", eip->islast ? tree_last : tree_norm, eip->dname);
}
/**
* Append a directory entry to a path and return
* new allocated pathname.
*/
static char*
concat(const char* path, const char* ent)
{
char* newpath;
size_t n = strlen(path);
newpath = Realloc(NULL, n + strlen(ent) + 2);
if (path[n - 1] == '/') /* don't add extra `/'s */
sprintf(newpath, "%s%s", path, ent);
else
sprintf(newpath, "%s/%s", path, ent);
return newpath;
}
/**
* Make a copy of dirent and stat structures associated with
* a file.
*/
static struct entinfo*
direntcpy(const struct dirent* dentp)
{
struct entinfo* eip;
eip = Realloc(NULL, sizeof(struct entinfo));
memset(eip, 0, sizeof(struct entinfo));
strcpy(eip->name, dentp->d_name);
eip->type = dentp->d_type;
if (eip->type == DT_UNKNOWN) { /* no short-cut */
struct stat st;
if (lstat(eip->name, &st) != -1) {
switch (st.st_mode & S_IFMT) {
case S_IFDIR: eip->type = DT_DIR; break;
case S_IFLNK: eip->type = DT_LNK; break;
}
}
}
conv(cd, eip->dname, eip->name);
eip->islast = false;
return eip;
}
/**
* Check if arg is `.' or `..'.
*/
static bool
isdotdir(const char* name)
{
return !strcmp(name, ".") || !strcmp(name, "..");
}
/**
* Check if entry is a directory.
*/
static bool
isdir(const struct entinfo* eip)
{
return eip->type == DT_DIR;
}
/**
* Check if 2 directory entries are the same.
*/
static int
dircmp(const void* ent1, const void* ent2)
{
struct entinfo* eip1 = *(struct entinfo** )ent1;
struct entinfo* eip2 = *(struct entinfo** )ent2;
return strcmp(eip1->name, eip2->name);
}
/**
* Convert UTF-8 names to current codeset.
*/
static void
conv(iconv_t ic, char* dest, char* src)
{
size_t slen = strlen(src);
size_t dlen = MAXNAMLEN;
char* s = src;
char* d = dest;
while (slen > 0) {
errno = 0;
size_t rc = iconv(ic, &s, &slen, &d, &dlen);
if (rc != (size_t)-1)
break;
if (errno == EILSEQ || errno == EINVAL) {
*d++ = '?';
s++;
dlen--;
slen--;
} else {
error("%s: iconv failed.", prog);
strcpy(dest, "?");
break;
}
}
}
/**
* Free the elements in a directory entry array
* and the array.
*/
static void
freeents(struct entinfo** eip, const size_t neip)
{
size_t i;
for (i = 0; i < neip; i++)
if (eip[i] != NULL)
free(eip[i]);
free(eip);
}
/**
* Process program options.
*/
static int
do_opts(int argc, char* argv[])
{
int opt;
/* defaults */
opts.dodot = false; /* don't show `.' entries */
opts.dofile = true; /* show both files & dirs. */
opts.dosort = true; /* sort directory entries */
opts.dovt = false; /* don't use VT100 graph. chars. */
while ((opt = getopt(argc, argv, "adfgh")) != -1)
switch (opt) {
case 'a': /* show entries starting with `.' */
opts.dodot = true;
break;
case 'd': /* show only dirs. */
opts.dofile = false;
break;
case 'f': /* don't sort directory entries */
opts.dosort = false;
break;
case 'g': /* use VT100 graphic chars. for tree */
opts.dovt = true;
break;
case 'h':
usage(stdout);
exit(EXIT_SUCCESS);
default:
usage(stderr);
exit(EXIT_FAILURE);
}
return optind;
}
/**
* realloc wrapper.
*/
static void*
Realloc(void* ptr, const size_t size)
{
void* p;
p = realloc(ptr, size);
if (p == NULL) {
error("%s: realloc() failed.", prog);
exit(EXIT_FAILURE);
}
return p;
}
/**
* Print usage information.
*/
static void
usage(FILE* fp)
{
fprintf(fp,
"Usage: %s [OPTION]... [DIRECTORY]\n\
Print a tree-like directory listing.\n\
\n\
-a Show entries starting with `.' (default: skip)\n\
-d Show only directories (default: files & dirs.)\n\
-f Do not sort entries\n\
-g Use VT100 graphic chars. to print tree\n\
-h Show this help message\n", prog);
}
/**
* Print an error message.
*/
static void
error(const char* fmt, ...)
{
va_list ap;
int e = errno;
fflush(stdout);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (e != 0) {
fprintf(stderr, " (%s)", strerror(e));
errno = 0;
}
fprintf(stderr, "\n");
fflush(stderr);
}