
/*
 * This program prints to the stdout the directory tree.
 * Each directory is sorted in the printout.
 * When no arguments are presented the directory tree of the current directory
 * is printed.
 * When only one argument is given, it prints the directory tree of the specifyed
 *  directory.
 *
 * This program produce a sorted output similar to the command
 * find . -print | sort
 * find . -type d -print | sort
 * Written By: Erez Strauss.
 *
 * srtree [-l] [-f] [directory]
 * Compile command:
 * gcc -I ~/utilities/tlib srtree.c -L ~/utilities/tlib -ltlib -o srtree
 *
 * Usage:
 *  The srtree command has the following form:
 *
 *   srtree [options] [directory name]
 *
 *  where the options are
 *   -d #  - Display only a given depth of the tree, so files or directories
 *           deeper in the tree from the given directory plus the given depth
 *           will not be shown.
 *   -r    - reverse the sort order.
 *   -u    - unsorted directories entries.
 *   -L    - follow symbolic links.
 *   -f    - display also files.
 *   -e    - Filter by regular expression.
 *   -E    - Those that not match the regular expression.
 *   -c name   - generate copy script to copy current directory into the specifyed directory.
 *                      This option is not for the simple user, look at the output before using it.
 *				 Be aware, doesn't work well (?) with special characters.
 *				 What about files with '\'' ?
 *  optional directory name, the default is current directory.
 * The copy script will copy the current directory into the target directory.
 * srtree -c trg_dir . | sh -f
 *
 */
# include <stdio.h>
# include <stdlib.h>
# include <dirent.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <string.h>
# include <sys/param.h>

# include "dpa.h"

extern int lstat (const char *path, struct stat *buf);
extern int stat (const char *path, struct stat *buf);
extern char* strdup (const char*);

static int strpcmp (void** a, void** b);
static int rev_strpcmp (void** a, void** b);
static void no_sort_dpa (void** void1, int (*void2)(void**, void**));
static void srtree (const char* dir_name, int depth);
static void dsrtree (const char* dir_name, int depth);

/* by default we don't go through symbolic links. */
int (*active_stat) (const char *path, struct stat *buf) = lstat;
/* default directory for display. */
static const char *dflt_dir = ".";
/* by defalt we don't display files. */
static int files = 0;
/* by defalt we go indefenetly. */
static int max_depth = -1; /* any depth */
/* base target directory, for sort & copy operation */
static const char* copy_base = 0;

int (*names_cmp)(void**, void**) = strpcmp;
void (*dpa_sort_p)(void**, int (*)(void**, void**)) = dpa_sort;

int
main (int argc, char** argv)
    {
    int          i;
    const char*  directory = dflt_dir;

    for (i = 1; i < argc; i++)
        {
        if ('-' == argv[i][0])
            {
            switch (argv[i][1])
                {
              case 'L':
                active_stat = stat;
                break;
              case 'f':
                files = 1;
                break;
              case 'r':
                names_cmp = rev_strpcmp;
                break;
              case 'u':
                dpa_sort_p = no_sort_dpa;
                break;
              case 'd':
                max_depth = atoi (argv[++i]);
                break;
              case 'c':         /* Copy script operation */
                files = 1;
                copy_base = strdup (argv[++i]);
                break;
              default:
                fprintf (stderr, "%s: unknown option '%s' !\n", argv[0], argv[i]);
                exit (1);
                }
            }
        else
            {
            directory = argv[i];
            }
        }
    /* First printout the directory name. */
    fprintf (stdout, "%s\n", directory);
    if (files)
        {
        srtree (directory, 0);
        }
    else
        {
        dsrtree (directory, 0);
        }
    return 0;
    }

int
strpcmp (void** a, void** b)
    {
    return strcmp ((char*)*a, (char*)*b);
    }

int
rev_strpcmp (void** a, void** b)
    {
    return - strcmp ((char*)*a, (char*)*b);
    }

static void
no_sort_dpa (void** void1, int (*void2)(void**, void**))
    {
    }

void
srtree (const char* dir_name, int depth)
    {
    DIR*            dir_fp;
    struct dirent*  entp;
    char**          names;
    int             i;
    int             n;
    int             nm_len = strlen (dir_name);
    char            path[4096];

    if (max_depth >= 0 && max_depth < depth)
        {
        return;
        }
    dir_fp = opendir (dir_name);
    if (!dir_fp)
        {
        return;
        }
    names = (char**)dpa_new (50);
    if (!names)
        {
        closedir (dir_fp);
        }
    strcpy (path, dir_name);
    if ('/' != path[nm_len - 1])
        {
        path[nm_len++] = '/';
        path[nm_len  ] = '\0';
        }
    while ((entp = readdir (dir_fp)) != 0)
        {
        names = (char**)dpa_add ((void**)names, strdup (entp->d_name));
        }
    dpa_sort_p ((void**)names, names_cmp);
    for (i = 0, n = dpa_in_use ((void**)names); i < n; i++)
        {
        struct stat stat_buff;

        if ('.' == names[i][0] && (!names[i][1] || '.' == names[i][1]))
            {
            continue;
            }
        strcpy (path + nm_len, names[i]);
        /* We must print The directory name before we enter deep into it. */

        if (active_stat (path, &stat_buff) != 0)
            {
            perror ("state failed");
            exit (1);
            }

        if (!copy_base)
            {
            fprintf (stdout, "%s\n", path);
            }
        else
            {
            /* Print the copy script part. */
            char new_file[1024];
            sprintf (new_file, "%s/%s", copy_base, path);
            if (S_ISDIR (stat_buff.st_mode))
                {
                /* If the file is directory, then we should create a new one. */
                printf ("echo 'Directory %s'\nmkdir -p %s\n", path, new_file);
                }
            else
                {
                /* Simple file, copy it */
                printf ("cp -p '%s' '%s'\n", path, new_file);
                }
            }
        if (S_ISDIR (stat_buff.st_mode))
            {
            srtree (path, depth + 1);
            }
        }
    for (i = 0; i < n; i++)
        {
        free (names[i]);
        }
    dpa_delete ((void**)names);
    closedir (dir_fp);
    }

void
dsrtree (const char* dir_name, int depth)
    {
    DIR*            dir_fp;
    struct dirent*  entp;
    char**          names;
    int             i;
    int             n;
    char            path[4096];
    struct stat     stat_buff;
    int             nm_len = strlen (dir_name);
    char            d_orig[1024];

    if (max_depth >= 0 && max_depth < depth)
        {
        return;
        }
    dir_fp = opendir (dir_name);
    if (!dir_fp)
        {
        return;
        }
    names = (char**)dpa_new (50);
    if (!names)
        {
        closedir (dir_fp);
        }
    getcwd (d_orig, sizeof (d_orig));
    chdir (dir_name);
    strcpy (path, dir_name);
    if ('/' != path[nm_len - 1])
        {
        path[nm_len++] = '/';
        path[nm_len  ] = '\0';
        }
    while ((entp = readdir (dir_fp)) != 0)
        {
        strcpy (path + nm_len, entp->d_name);
        if (active_stat (entp->d_name, &stat_buff) == 0 && S_ISDIR (stat_buff.st_mode) && !S_ISLNK(stat_buff.st_mode))
            {
            if ('.' == entp->d_name[0]
                && (!entp->d_name[1] || '.' == entp->d_name[1]))
                {
                continue;
                }
            names = (char**)dpa_add ((void**)names, strdup (entp->d_name));
            }
        }
    dpa_sort_p ((void**)names, names_cmp);
    for (i = 0, n = dpa_in_use ((void**)names); i < n; i++)
        {
        strcpy (path + nm_len, names[i]);
        fprintf (stdout, "%s\n", path);
        chdir (d_orig);
        dsrtree (path, depth + 1);
        }
    for (i = 0; i < n; i++)
        {
        free (names[i]);
        }
    dpa_delete ((void**)names);
    closedir (dir_fp);
    }
