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 <limits.h> /* for PATH_MAX */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* for getopt() */
/* Declarations */
struct entinfo { /* describes a directory entry */
struct dirent dent; /* for entry name and type */
int islast; /* last entry in a directory if 1 */
};
/* Functions */
static char* concat(const char*, const char*);
static int dircmp(const void*, const void*);
static int do_opts(const int, char*[]);
static int isdir(const struct entinfo*);
static int isdot(const char*);
static int walk(const char*, const int, int*);
static size_t getents(const char*, struct entinfo***);
static struct entinfo* direntcpy(const struct dirent*);
static void error(const char*, ...);
static void inittchars(void);
static void freeents(struct entinfo**, const size_t);
static void prtree(const struct entinfo*, const int, const int*);
static void setlastdent(struct entinfo**, const size_t);
static void setlastfent(struct entinfo**, const size_t);
static void usage(void);
static void* Realloc(void*, const size_t);
/* Globals */
static char* prog; /* program name */
static struct options { /* program options */
int allents; /* show files in addition to dirs. if 1 */
int nosort; /* do not sort directory entries if 1 */
int vtgraph; /* use VT100 graph. chars. for tree if 1 */
} opts;
static const char dirsep = '/'; /* directory separator character */
static const char* tree_vbar; /* string/esc. sequence for vertical bar */
static const char* tree_norm; /* string/esc. sequence for normal entries */
static const char* tree_last; /* string/esc. sequence for last entry in dir. */
/**
* M A I N
*/
int
main(int argc, char* argv[])
{
char* path;
int branch[PATH_MAX]; /* print vert. bar in level if 1 */
int argidx;
prog = argv[0];
memset(branch, 0, sizeof branch);
argidx = do_opts(argc, argv);
argc -= argidx;
argv += argidx;
path = (argc == 0 || **argv == '\0') ? "." : *argv;
inittchars(); /* setup strings used for tree printing */
printf("%s:\n", path);
return walk(path, 0, branch);
}
/**
* Setup the strings used for tree printing.
*/
static void
inittchars(void)
{
if (opts.vtgraph == 1) { /* use VT100 graph. chars. for tree */
tree_vbar = "\033(0x\033(B";
tree_norm = "\033(0tqq\033(B";
tree_last = "\033(0mqq\033(B";
} else { /* use plain ASCII chars. for tree */
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, int* branch)
{
struct entinfo** eip; /* file/dir entries in the current directory */
size_t neip; /* no. of entries in array `eip' */
size_t i; /* current directory entry number */
/*
* Get list of entries in specified directory.
*/
neip = getents(path, &eip);
if ((ssize_t)neip == -1) /* error */
return EXIT_FAILURE;
/*
* Sort the entries in lexicographic order.
*/
if (opts.nosort != 1)
qsort(eip, neip, sizeof(struct entinfo* ), dircmp);
/*
* Mark the last entry or the last directory entry
* in the array after sorting.
*/
if (opts.allents == 1)
setlastfent(eip, neip);
else
setlastdent(eip, neip);
for (i = 0; i < neip; i++) {
/*
* Check if entry is a directory and also not a symlink.
*/
if (isdir(eip[i]) == 1) {
char* newpath;
/*
* If this is the last entry in a directory,
* then no more branches are possible. So we
* don't print any branch chars for this level.
*/
if (eip[i]->islast == 1)
branch[level] = 0; /* no branch */
else
branch[level] = 1; /* print branch */
prtree(eip[i], level, branch);
/* recurse */
newpath = concat(path, eip[i]->dent.d_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 printing of branches.
*/
else if (opts.allents == 1)
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 size_t
getents(const char* path, struct entinfo*** eip)
{
DIR* dir; /* current directory */
struct entinfo** ents; /* entries in the current directory */
struct dirent* dent; /* directory entry */
size_t entsz; /* size of array `ents' */
size_t nent; /* current directory entry number */
dir = opendir(path);
if (dir == NULL) {
error("%s: %s: can't read directory.", prog, path);
*eip = NULL; /* nothing read */
return (ssize_t)-1; /* error */
}
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 (isdot(dent->d_name) == 0) {
struct entinfo* e;
if ((e = direntcpy(dent)) != NULL)
ents[nent++] = e;
}
}
closedir(dir);
*eip = ents; /* store pointer to entries in current dir. */
return nent; /* return no. of entries in current dir. */
}
/**
* Mark the last entry in the array `eip'.
*/
static void
setlastfent(struct entinfo** eip, const size_t neip)
{
if (neip > 0)
eip[neip - 1]->islast = 1;
}
/**
* Mark the last _directory_ entry in the array `eip'.
*/
static void
setlastdent(struct entinfo** eip, const size_t neip)
{
if (neip > 0) {
size_t i, lastdir; /* tmp, index to last directory entry */
lastdir = 0;
for (i = 0; i < neip; i++)
if (isdir(eip[i]) == 1)
lastdir = i;
eip[lastdir]->islast = 1;
}
}
/**
* 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 int* branch)
{
int i;
printf("\t");
for (i = 0; i < level; i++)
if (branch[i] == 1)
printf("%s ", tree_vbar);
else
printf(" ");
printf("%s ", eip->islast ? tree_last : tree_norm);
printf("%s\n", eip->dent.d_name);
}
/**
* Append a directory entry to a path and return
* new allocated pathname.
*/
static char*
concat(const char* path, const char* ent)
{
char* newpath;
newpath = Realloc(NULL, strlen(path) + strlen(ent) + 2);
if (path[strlen(path) - 1] != dirsep) /* don't add extra slashes */
sprintf(newpath, "%s%c%s", path, dirsep, 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));
#define FIX_CLANG_ADDRESS_SANITIZER_ERROR 1
#if FIX_CLANG_ADDRESS_SANITIZER_ERROR
strcpy(eip->dent.d_name, dentp->d_name);
eip->dent.d_type = dentp->d_type;
#else
eip->dent = *dentp;
// OR
// memcpy(&eip->dent, dentp, sizeof (struct dirent));
#endif
if (eip->dent.d_type == DT_UNKNOWN) {
struct stat st;
if (lstat(eip->dent.d_name, &st) != -1) {
switch (st.st_mode & S_IFMT) {
case S_IFDIR: eip->dent.d_type = DT_DIR; break;
case S_IFLNK: eip->dent.d_type = DT_LNK; break;
}
}
}
eip->islast = 0;
return eip;
}
/**
* Check if arg is `.' or `..'.
*/
static int
isdot(const char* name)
{
return !strcmp(name, ".") || !strcmp(name, "..");
}
/**
* Check if entry is a directory and also not a symlink.
*/
static int
isdir(const struct entinfo* eip)
{
return eip->dent.d_type==DT_DIR && eip->dent.d_type!=DT_LNK;
}
/**
* Check if 2 directory entries are the same.
*/
static int
dircmp(const void* ent1, const void* ent2)
{
struct entinfo* eip1;
struct entinfo* eip2;
eip1 = *(struct entinfo**)ent1;
eip2 = *(struct entinfo**)ent2;
return strcmp(eip1->dent.d_name, eip2->dent.d_name);
}
/**
* Free the elements in a directory entry array
* and the array.
*/
static void
freeents(struct entinfo** eip, const size_t neip)
{
size_t i;
if (eip != NULL) {
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.allents = 0; /* don't show files */
opts.nosort = 0; /* sort directory entries */
opts.vtgraph = 0; /* use plain ASCII chars for tree */
while ((opt = getopt(argc, argv, "afg")) != -1)
switch (opt) {
case 'a': /* show files too */
opts.allents = 1;
break;
case 'f': /* don't sort directory entries */
opts.nosort = 1;
break;
case 'g': /* use VT100 graphic chars for tree */
opts.vtgraph = 1;
break;
default:
usage();
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(void)
{
fprintf(stderr, "Usage: %s [OPTION] [DIRECTORY]\n", prog);
fprintf(stderr, "%s: Print a tree like directory listing\n", prog);
fprintf(stderr, "\n");
fprintf(stderr, " -a Print all entries (default: dirs. only)\n");
fprintf(stderr, " -f Do not sort entries\n");
fprintf(stderr, " -g Use VT100 graphic chars to print tree\n");
}
/**
* Print an error message.
*/
static void
error(const char* fmt, ...)
{
va_list ap;
fflush(stdout);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (errno != 0) {
fprintf(stderr, " %s", strerror(errno));
errno = 0;
}
fprintf(stderr, "\n");
fflush(stderr);
}