/* scandir.c              Copyright (C) Codemist Ltd., 1994-2002 */

/*
 *
 * Directory scanning code for use in CSL-related utilities.
 *
 *                        A C Norman
 */

/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */


/* Signature: 163eeafe 10-Oct-2002 */

#include "sys.h"

/*
 * If I am to process directories I need a set of routines that will
 * scan sub-directories for me.  This is (necessarily?) dependent on
 * the operating system I am running under, hence the conditional compilation
 * here.  The specification I want is:
 *       void scan_directory(char *dir,
 *                    void (*proc)(char *name, int why, long int size));
 *
 * This is called with a file- or directory-name as its first argument
 * and a function as its second.
 * It calls the function for every directory and every file that can be found
 * rooted from the given place.  If the file to scan is specified as NULL
 * the current directory is processed. I also arrange that an input string
 * "." (on Windows, DOS and Unix) or "@" (Archimedes) is treated as a request
 * to scan the whole of the current directory.
 * When a simple file is found the procedure is called with the name of the
 * file, why=0, and the length (in bytes) of the file.  For a directory
 * the function is called with why=1, then the contents of the directory are
 * processed. For directories the size information will be 0.  There is no
 * guarantee of useful behaviour if some of the files to be scanned are
 * flagged as  "invisible" or "not readable" or if they are otherwise special.
 *
 * I also provide a similar function scan_files() with the same arguments that
 * does just the same except that it does not recurse into sub-directories,
 * but if the name originally passed is that of a directory then all the
 * files in it will be scanned.
 */

/*
 * When scan_directory calls the procedure it has been passed, it will have
 * set scan_leafstart to the offset in the passed filename where the
 * original directory ended and the new information starts.
 */

int scan_leafstart = 0;

/* #define SCAN_FILE       0 */  /* In scandir.h - listed here for emphasis */
/* #define SCAN_STARTDIR   1 */
/* #define SCAN_ENDDIR     2 */

/*
 * I use a (static) flag to indicate how sub-directories should be
 * handled, and what to do about case. By default I fold to lower case
 * on windows. setting hostcase non-zero causes case to be preserved.
 */

static int recursive_scan, hostcase = 0;

/*
 * The following is an expression of despair! ANSI C (all the way back from
 * 1989, and based on good practise from before then, mandates that
 * realloc should behave as malloc if its first arg is NULL. But STILL there
 * are C libraries out there which do not honour this and which crash
 * in such cases. Thus this veneer ought to be unnecessary but in reality
 * is a useful safety net!
 */
static void *my_realloc(void *p, int len)
{
   if (p == NULL) return (*malloc_hook)(len);
   else return (*realloc_hook)(p, len);
}

void set_hostcase(int fg)
{
    hostcase = fg;
}

#ifdef __LCC__

static char filename[LONGEST_LEGAL_FILENAME];

static struct _finddata_t *found_files = NULL;
static int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   struct _finddata_t *fnew = (struct _finddata_t *)
            my_realloc((void *)found_files,
                       sizeof(struct _finddata_t) *
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1; /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

int MS_CDECL alphasort_files(const void *a, const void *b)
{
    const struct _finddata_t *fa = (const struct _finddata_t *)a,
                      *fb = (const struct _finddata_t *)b;
    return strncmp(fb->name, fa->name, sizeof(fa->name));
}

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    int rootlen = namelength;
    long code;
	int first = n_found_files;
    long fh;
    struct _finddata_t found;
    fh = _findfirst(filename, &found);
    code = fh;
    while (code != -1)
    {   if (more_files()) break;
        found_files[n_found_files++] = found;
        code = _findnext(fh, &found);
    }
    _findclose(fh);
    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(struct _finddata_t),
          alphasort_files);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
    while (n_found_files != first)
    {   char *p = (char *)&found_files[--n_found_files].name;
        int c;
        namelength = rootlen+1;
/*
 * I fold DOS filenames into lower case because it does not matter much
 * to DOS and I think it looks better - furthermore it helps when I move
 * archives to other systems.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) if (isupper(c)) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
        if (found_files[n_found_files].attrib & _A_SUBDIR)
        {   if (found_files[n_found_files].name[0] != '.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE, found_files[n_found_files].size);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   strcpy(filename, "*.*");
        scan_leafstart = 0;
    }
    else
    {   scan_leafstart = strlen(dir);
        strcpy(filename, dir);
        strcpy(filename+scan_leafstart, "\\*.*");
        scan_leafstart++;
    }
    exall(strlen(filename), proc);
}

#define SCANDIR_KNOWN 1

#else
#ifdef WINDOWS_NT

/*
 * Note that I put the test for WINDOWS_NT before the test for MS_DOS
 * so that if it happens that the symbol MS_DOS is defined that fact will
 * not cause too much confusion.
 */

static char filename[LONGEST_LEGAL_FILENAME];

static WIN32_FIND_DATA *found_files = NULL;
static int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   WIN32_FIND_DATA *fnew = (WIN32_FIND_DATA *)
            my_realloc((void *)found_files,
                       sizeof(WIN32_FIND_DATA)*
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1;  /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

/*
 * Anybody compiling using Microsoft Visual C++ had better note that
 * the type declared in the Microsoft header files for qsort insists
 * on a __cdecl here. Ugh.
 */
int MS_CDECL alphasort_files(const void *a, const void *b)
{
    const WIN32_FIND_DATA *fa = (const WIN32_FIND_DATA *)a,
                          *fb = (const WIN32_FIND_DATA *)b;
    return strncmp(fb->cFileName, fa->cFileName, sizeof(fa->cFileName));
}

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    WIN32_FIND_DATA found;
    int rootlen = namelength, first = n_found_files;
    HANDLE hSearch = FindFirstFile(filename, &found);
    if (hSearch == INVALID_HANDLE_VALUE) return;  /* No files found at all */
    for (;;)
    {   if (more_files()) break;
        found_files[n_found_files++] = found;
        if (!FindNextFile(hSearch, &found)) break;
    }
    FindClose(hSearch);
    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(WIN32_FIND_DATA),
          alphasort_files);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
    while (n_found_files != first)
    {   char *p = (char *)&found_files[--n_found_files].cFileName;
        int c;
/*
 * Fill out filename with the actual name I grabbed, i.e. with
 * wild-cards expanded.
 */
        namelength = rootlen+1;
/*
 * I fold DOS filenames into lower case because it does not matter much
 * to DOS and I think it looks better - furthermore it helps when I move
 * archives to other systems.  So I do the same on NT.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) if (isupper(c)) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
        if (found_files[n_found_files].dwFileAttributes &
            FILE_ATTRIBUTE_DIRECTORY)
        {   if (found_files[n_found_files].cFileName[0] != '.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE,
                  found_files[n_found_files].nFileSizeLow);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   strcpy(filename, "*.*");
        scan_leafstart = 0;
    }
    else
    {   scan_leafstart = strlen(dir);
        strcpy(filename, dir);
        if (filename[scan_leafstart-1] == '\\')
        {   /* Root directory */
            strcpy(filename+scan_leafstart, "*.*");
            --scan_leafstart;
        }
        else strcpy(filename+scan_leafstart, "\\*.*");
        scan_leafstart++;
    }
    exall(strlen(filename), proc);
}

#define SCANDIR_KNOWN 1

#else  /* WINDOWS_NT */

#ifdef MS_DOS

/*
 * This will be OK for both Zortech and Watcom. The entry for GCCWIN refers
 * to Cygnus cygwin32 "Windows GCC" together with the windows32api header
 * files and libraries. However note well that the cygwin32 version of this
 * code has not yet been got working and the status of various bits of
 * Windows code in that environment remains slightly delicate as of 1Q97.
 */

#if defined __WATCOMC__ || defined _MSC_VER || defined GCCWIN

static char filename[LONGEST_LEGAL_FILENAME];

static WIN32_FIND_DATA *found_files = NULL;
static int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   WIN32_FIND_DATA *fnew = (WIN32_FIND_DATA *)
            my_realloc((void *)found_files,
                       sizeof(WIN32_FIND_DATA) *
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1; /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

int MS_CDECL alphasort_files(const void *a, const void *b)
{
    const WIN32_FIND_DATA *fa = (const WIN32_FIND_DATA *)a,
                      *fb = (const WIN32_FIND_DATA *)b;
    return strncmp(fb->cFileName, fa->cFileName, sizeof(fa->cFileName));
}

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    int rootlen = namelength;
    int code, first = n_found_files;
    HANDLE fh;
    WIN32_FIND_DATA found;
    fh = FindFirstFile(filename, &found);
    code = (fh != INVALID_HANDLE_VALUE);
    while (code)
    {   if (more_files()) break;
        found_files[n_found_files++] = found;
        code = FindNextFile(fh, &found);
    }
    FindClose(fh);
    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(WIN32_FIND_DATA),
          alphasort_files);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
    while (n_found_files != first)
    {   char *p = (char *)&found_files[--n_found_files].cFileName;
        int c;
/*
 * Fill out filename with the actual name I grabbed, i.e. with
 * wild-cards expanded.
 */
        namelength = rootlen+1;
/*
 * I fold DOS filenames into lower case because it does not matter much
 * to DOS and I think it looks better - furthermore it helps when I move
 * archives to other systems.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) if (isupper(c)) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
        if (found_files[n_found_files].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {   if (found_files[n_found_files].cFileName[0] != '.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE, found_files[n_found_files].nFileSizeLow);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   strcpy(filename, "*.*");
        scan_leafstart = 0;
    }
    else
    {   scan_leafstart = strlen(dir);
        strcpy(filename, dir);
        strcpy(filename+scan_leafstart, "\\*.*");
        scan_leafstart++;
    }
    exall(strlen(filename), proc);
}

#else
#ifdef GCC386

/*
 * BEWARE: this version will not recognise Windows 95 long file-names
 * and so there is very real possibility of muddle.
 */

static char filename[LONGEST_LEGAL_FILENAME];

static struct ffblk *found_files = NULL;
static int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   struct ffblk *fnew = (struct ffblk *)
            my_realloc((void *)found_files,
                       sizeof(struct ffblk) *
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1; /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

int alphasort_files(const void *a, const void *b)
{
    const struct ffblk *fa = (const struct ffblk *)a,
                      *fb = (const struct ffblk *)b;
    return strncmp(fb->ff_name, fa->ff_name, sizeof(fa->ff_name));
}

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    int rootlen = namelength;
    int code, first = n_found_files;
    struct ffblk found;
    code = findfirst(filename, &found, FA_DIREC);
    while (code == 0)
    {   if (more_files()) break;
        found_files[n_found_files++] = found;
        code = findnext(&found);
    }
    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(struct ffblk),
          alphasort_files);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
    while (n_found_files != first)
    {   char *p = (char *)&found_files[--n_found_files].ff_name;
        int c;
/*
 * Fill out filename with the actual name I grabbed, i.e. with
 * wild-cards expanded.
 */
        namelength = rootlen+1;
/*
 * I fold DOS filenames into lower case because it does not matter much
 * to DOS and I think it looks better - furthermore it helps when I move
 * archives to other systems.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) if (isupper(c)) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
        if (found_files[n_found_files].ff_attrib & FA_DIREC)
        {   if (found_files[n_found_files].ff_name[0] != '.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE, found_files[n_found_files].ff_fsize);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   strcpy(filename, "*.*");
        scan_leafstart = 0;
    }
    else
    {   scan_leafstart = strlen(dir);
        strcpy(filename, dir);
        strcpy(filename+scan_leafstart, "\\*.*");
        scan_leafstart++;
    }
    exall(strlen(filename), proc);
}

#else /* __WATCOMC__ */

static char filename[LONGEST_LEGAL_FILENAME];

static struct find_t *found_files = NULL;
static int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   struct find_t *fnew = (struct find_t *)
            my_realloc((void *)found_files,
                       sizeof(struct find_t) *
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1; /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

int alphasort_files(const void *a, const void *b)
{
    const struct find_t *fa = (const struct find_t *)a,
                      *fb = (const struct find_t *)b;
    return strncmp(fb->name, fa->name, sizeof(fa->name));
}

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    int rootlen = namelength;
    int code, first = n_found_files;
    struct find_t found;
#ifdef FA_DIREC
    code = _dos_findfirst(filename, FA_DIREC, &found);
#else
    code = _dos_findfirst(filename, _A_SUBDIR, &found);
#endif
    while (code == 0)
    {   if (more_files()) break;
        found_files[n_found_files++] = found;
        code = _dos_findnext(&found);
    }
    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(struct find_t),
          alphasort_files);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
    while (n_found_files != first)
    {   char *p = (char *)&found_files[--n_found_files].name;
        int c;
/*
 * Fill out filename with the actual name I grabbed, i.e. with
 * wild-cards expanded.
 */
        namelength = rootlen+1;
/*
 * I fold DOS filenames into lower case because it does not matter much
 * to DOS and I think it looks better - furthermore it helps when I move
 * archives to other systems.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) if (isupper(c)) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
#ifdef FA_DIREC
        if (found_files[n_found_files].attribute & FA_DIREC)
#else
        if (found_files[n_found_files].attrib & _A_SUBDIR)
#endif
        {   if (found_files[n_found_files].name[0] != '.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE, found_files[n_found_files].size);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   strcpy(filename, "*.*");
        scan_leafstart = 0;
    }
    else
    {   scan_leafstart = strlen(dir);
        strcpy(filename, dir);
        strcpy(filename+scan_leafstart, "\\*.*");
        scan_leafstart++;
    }
    exall(strlen(filename), proc);
}

#endif /* __WATCOMC__ */
#endif /* GCC386 */

#define SCANDIR_KNOWN 1

#else  /* MS_DOS */

#ifdef UNIX

static char filename[LONGEST_LEGAL_FILENAME];

/*
 * The code here uses opendir, readdir and closedir and as such ought to
 * be Posix compatible. The macro USE_DIRECT_H can cause an older variant
 * on this idea to be used. BUt it may need adjustment for different
 * systems.
 */

#ifdef USE_DIRECT_H
#include <sys/types.h>
#include <sys/dir.h>
#else
#include <dirent.h>
#endif

static char **found_files = NULL;

int n_found_files = 0, max_found_files = 0;

#define TABLE_INCREMENT 50

static int more_files(void)
{
    if (n_found_files > max_found_files - 5)
    {   char **fnew = (char **)
            my_realloc((void *)found_files,
                       sizeof(char *) *
                          (max_found_files + TABLE_INCREMENT));
        if (fnew == NULL) return 1;  /* failure flag */
        found_files = fnew;
        max_found_files += TABLE_INCREMENT;
    }
    return 0;
}

int alphasort_files(const void *a, const void *b)
{
    const char *fa = *(const char **)a,
               *fb = *(const char **)b;
    return strcmp(fb, fa);
}

extern int stat(const char *, struct stat*);

static void scan_file(int namelength,
                      void (*proc)(char *name, int why, long int size));

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
{
    DIR *d;
#ifdef USE_DIRECT_H
    struct direct *dd;
#else
    struct dirent *dd;
#endif
    int i, j;
    int rootlen = namelength, first = n_found_files;
    proc(filename, SCAN_STARTDIR, 0);
    d = opendir(filename);
    if (d != NULL)
    {   while ((dd = readdir(d)) != NULL)
        {   char *leafname = dd->d_name;
            char *copyname;
/*
 * readdir hands back both "." and ".." but I had better not recurse
 * into either!
 */
            if (strcmp(leafname, ".") == 0 ||
                strcmp(leafname, "..") == 0) continue;
            if (more_files()) break;
            copyname = (char *)malloc(1+strlen(leafname));
            if (copyname == NULL) break;
            strcpy(copyname, leafname);
            found_files[n_found_files++] = copyname;
        }
        closedir(d);
    }

    qsort((void *)&found_files[first],
          n_found_files-first,
          sizeof(char *),
          alphasort_files);

    filename[rootlen] = '/';
    while (n_found_files != first)
    {   char *p = found_files[--n_found_files];
        int c;
        namelength = rootlen+1;
        while ((c = *p++) != 0) filename[namelength++] = (char)c;
        free((void *)found_files[n_found_files]);
        filename[namelength] = 0;
        scan_file(namelength, proc);
    }

    filename[rootlen] = 0;
    proc(filename, SCAN_ENDDIR, 0);
}

#ifndef S_IFMT
# ifdef __S_IFMT
#  define S_IFMT __S_IFMT
# endif
#endif

#ifndef S_IFDIR
# ifdef __S_IFDIR
#  define S_IFDIR __S_IFDIR
# endif
#endif

#ifndef S_IFREG
# ifdef __S_IFREG
#  define S_IFREG __S_IFREG
# endif
#endif

static void scan_file(int namelength,
                      void (*proc)(char *name, int why, long int size))
{
    struct stat buf;
    stat(filename, &buf);
    if ((buf.st_mode & S_IFMT) == S_IFDIR)
    {   if (!recursive_scan) proc(filename, SCAN_STARTDIR, 0);
        else exall(namelength, proc);
    }
    else if ((buf.st_mode & S_IFMT) == S_IFREG)
        proc(filename, SCAN_FILE, buf.st_size);
/*  else fprintf(stderr, "Mode of %s is %o\n", filename, buf.st_mode); */
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir, ".")==0) dir = ".";
    scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    scan_file(scan_leafstart-1, proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir, ".")==0) dir = ".";
    scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(scan_leafstart-1, proc);
}

#define SCANDIR_KNOWN 1

#else /* UNIX */

#ifdef __arm

/*
 * I want to be able to find the currently-selected directory - if
 * only so I can restore it at the end of processing.  Yes I do know
 * that RISCOS is moving away from the concept of a current directory,
 * but this is a command-line utility at present, at least.
 */

static void find_current_directory(char dir[])
{
    os_gbpbstr block;
    char drivename[64];
    char name[32];
/*
 * osgbpb(5) reads the name of the current disc - which I need as the first
 * part of a full-rooted file-path.
 */
    block.action = 5;
    block.file_handle = 0;
    block.data_addr = drivename;
    os_gbpb(&block);
    for (;;)
    {   int len;
/*
 * osgbpb(6) reads the name of the currently selected directory - but just
 * the trailing component.
 */
        block.action = 6;
        block.file_handle = 0;
        block.data_addr = name;
        os_gbpb(&block);
        len = name[1];
        /* Here I trim trailing blanks from the name */
        while (name[len + 1] == ' ' && len > 0) len--;
        if (dir[0] == 0)
        {   memcpy(dir, &name[2], len);
            dir[len] = 0;
        }
        else
        {   memmove(&dir[1 + len], dir, strlen(dir) + 1);
            memcpy(dir, &name[2], len);
            dir[len] = '.';
        }
/*
 * When I find that the director name is '$' I conclude that I must be
 * at the top of the tree, and I exit.
 */
        if (len == 1 && name[2] == '$')
        {   len = drivename[0];
            memmove(&dir[2 + len], dir, strlen(dir) + 1);
            memcpy(&dir[1], &drivename[1], len);
            dir[0] = ':';
            dir[len+1] = '.';
            return;
        }
/*
 * Mostly I select the parent directory, and by reading names of
 * successive parents I build up a complete name.
 */
        system("dir ^");
    }
}

#define AT_ONCE                 80
#define NAME_LENGTH             12

static char filename[LONGEST_LEGAL_FILENAME];
static char dirsave[LONGEST_LEGAL_FILENAME+4];
            /* Original directory the user had selected */

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    os_filestr file_status;
/*
 * osfile(5) reads information about a file - in particular it allows me to
 * tell if I have a simple file or a directory.
 */
    file_status.action = 5;
    file_status.name = filename;
    if (os_file(&file_status) != NULL) file_status.action = 99;
    switch (file_status.action)
    {
default:
/*      fprintf(stderr, "\nBad return code from osfile\n");    */
        return;
/* case 0: fprintf(stderr, "\nFile %s not found\n", filename); */
/*         exit(EXIT_FAILURE);                                 */
case 0xff:                                                /* Protected file */
case 1: proc(filename, SCAN_FILE, file_status.start);     /* Simple file */
        return;
case 2: /* Here we have a directory to scan */
        proc(filename, SCAN_STARTDIR, 0);
        if (!recursive_scan) return;

        {   char workvec[NAME_LENGTH*AT_ONCE];
            os_gbpbstr block;
            block.seq_point = 0;
            for (;;)
            {   char *p = workvec;
                block.action = 9;
                block.file_handle = (int)filename;/* Scan given directory */
                block.data_addr = workvec;
                block.number = AT_ONCE;
                block.buf_len = NAME_LENGTH*AT_ONCE;
                block.wild_fld = "*";
                filename[namelength] = 0;
/*
 * osgbpb(9) reads (several) file-names from the specified directory.
 */
                os_gbpb(&block);
                if (block.number == 0) break;     /* Nothing transfered */
                while (block.number != 0)
                {   int len = strlen(p);
                    filename[namelength] = '.';
                    strcpy(&filename[namelength+1], p);
                    p += len + 1;
/*
 * I concatenate the name of the new directory on the end of my current path
 * and recurse into it.
 */
                    exall(namelength + len + 1, proc);
                    block.number--;
                }
                if (block.seq_point == -1) break;
            }

            filename[namelength] = 0;
        }
        proc(filename, SCAN_ENDDIR, 0);
        return;
    }
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    strcpy(dirsave, "dir ");
    find_current_directory(dirsave+4);  /* Find initial directory */
    system(dirsave);                    /* Restore initial directory */
    strcpy(filename, dir==NULL ? "@" : dir);
    scan_leafstart = strcmp(dir, "@")==0 ? 2 : 0;
    exall(strlen(filename), proc);
    system(dirsave);                    /* Restore initial directory */
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    strcpy(dirsave, "dir ");
    find_current_directory(dirsave+4);  /* Find initial directory */
    system(dirsave);                    /* Restore initial directory */
    strcpy(filename, dir==NULL ? "@" : dir);
    scan_leafstart = strcmp(dir, "@")==0 ? 2 : 0;
    exall(strlen(filename), proc);
    system(dirsave);                    /* Restore initial directory */
}

#define SCANDIR_KNOWN 1

#else /* __arm */

#ifdef ATARI

static char filename[LONGEST_LEGAL_FILENAME];

static void exall(int32 namelength,
                  void (*proc)(char *name, int why, long int size))
/*
 * This procedure scans a directory-full of files, calling the given procedure
 * to process each one it finds.
 */
{
    int32 rootlen = namelength;
    int code;
    struct FILEINFO found;

    code = dfind(&found, filename, FA_DIREC);
    while (rootlen>=0 && filename[rootlen]!='\\') rootlen--;
/*
 * If I used _dos_findfirst I could allow recursion here,
 * and if I call findfirst(filename, FA_DIREC) then directories can
 * be found as well as just files.
 */
    while (code == 0)
    {   char *p = (char *)&found.name;
        int c;
/*
 * Fill out filename with the actual name I grabbed, i.e. with
 * wild-cards expanded.
 */
        namelength = rootlen+1;
/*
 * I fold Atari filenames into lower case because it does not matter much
 * to Atari and I think it looks better - furthermore it helps when I move
 * archives to other systems.
 */
        while ((c = *p++) != 0)
        {   if (!hostcase) c = tolower(c);
            filename[namelength++] = (char)c;
        }
        filename[namelength] = 0;
        if (found.attr & FA_DIREC)
        {   if (found.name[0]!='.')
/*
 * I filter out directory names that start with '.'.
 * This is to avoid calamity with recursion though chains such as .\.\.\.....
 */
            {   proc(filename, SCAN_STARTDIR, 0);
                if (!recursive_scan) continue;

                strcpy(&filename[namelength], "\\*.*");
/*
 * Append "\*.*" to the directory-name and try again, thereby scanning
 * its contents.
 */
                exall(namelength+4, proc);
                filename[namelength] = 0;
                proc(filename, SCAN_ENDDIR, 0);
            }
        }
        else proc(filename, SCAN_FILE, found.size);
        code = dnext(&found);
    }
    return;
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir,".")==0)
    {   dir = "*.*";
        scan_leafstart = 0;
    }
    else scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    exall(strlen(filename), proc);
}

#define SCANDIR_KNOWN 1

#else  /* ATARI */
#ifdef macintosh
/*
 * I have place-holder routines here, so that at some stage proper versions
 * can be written and installed. By leaving the Unix code in as comment I hope
 * to help whoever gets around to building the Macintosh-specific versions...
 */

static char filename[LONGEST_LEGAL_FILENAME];

#ifdef THIS_NEEDS_RE_WRITING_FOR_MACINTOSH
static int remove_dot(struct direct *name)
{
  if (strcmp(name->d_name, ".")==0 || strcmp(name->d_name, "..")==0) return 0;
  return 1;
}
#endif

/* int alphasort(struct direct **, struct direct **);  */
/* extern int stat(const char *, struct stat*);        */

static void scan_file(int namelength,
                      void (*proc)(char *name, int why, long int size));

static void exall(int namelength,
                  void (*proc)(char *name, int why, long int size))
{
#ifdef THIS_NEEDS_RE_WRITING_FOR_MACINTOSH
    struct direct **namelist;
    int i, j;
    proc(filename, SCAN_STARTDIR, 0);
    if (!recursive_scan) return;

    i = scandir(filename, &namelist, remove_dot, alphasort);
    for(j=0; j<i; j++)
    {   char *leafname = namelist[j]->d_name;
        filename[namelength] = '/';
        strcpy(&filename[namelength+1], leafname);
        scan_file(namelength+1+strlen(leafname), proc);
        free(namelist[j]);
    }
    free(namelist);
    filename[namelength] = 0;
    proc(filename, SCAN_ENDDIR, 0);
#endif
}

static void scan_file(int namelength,
                      void (*proc)(char *name, int why, long int size))
{
#ifdef THIS_NEEDS_RE_WRITING_FOR_MACINTOSH
    struct stat buf;
    stat(filename, &buf);
    if ((buf.st_mode & S_IFMT) == S_IFDIR) exall(namelength, proc);
    else if ((buf.st_mode & S_IFMT) == S_IFREG)
        proc(filename, SCAN_FILE, buf.st_size);
/*  else fprintf(stderr, "Mode of %s is %o\n", filename, buf.st_mode); */
#endif
}

void scan_directory(char *dir,
                    void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 1;
    if (dir==NULL || strcmp(dir, ".")==0) dir = ".";
    scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    scan_file(scan_leafstart-1, proc);
}

void scan_files(char *dir,
                void (*proc)(char *name, int why, long int size))
{
    recursive_scan = 0;
    if (dir==NULL || strcmp(dir, ".")==0) dir = ".";
    scan_leafstart = strlen(dir)+1;
    strcpy(filename, dir);
    scan_file(scan_leafstart-1, proc);
}

#define SCANDIR_KNOWN 1

#endif /* macintosh */
#endif /* ATARI */
#endif /* __arm */
#endif /* UNIX */
#endif /* MS_DOS */
#endif /* WINDOWS_NT */
#endif /* __LCC__ */

#ifndef SCANDIR_KNOWN
#error "Directory scanning machine-specific code not present"
#endif

/* end of scandir.c */


