/*
 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

/*  This file is ALSO:
 *  Copyright 2001-2004 David Abrahams.
 *  Copyright 2005 Rene Rivera.
 *  Distributed under the Boost Software License, Version 1.0.
 *  (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 */

# include "jam.h"
# include "filesys.h"
# include "strings.h"
# include "pathsys.h"
# include "newstr.h"
# include <stdio.h>
# include <sys/stat.h>

#if defined(sun) || defined(__sun) || defined(linux)
# include <unistd.h> /* needed for read and close prototype */
#endif

# ifdef USE_FILEUNIX

#if defined(sun) || defined(__sun)
# include <unistd.h> /* needed for read and close prototype */
#endif

# if defined( OS_SEQUENT ) || \
     defined( OS_DGUX ) || \
     defined( OS_SCO ) || \
     defined( OS_ISC )
# define PORTAR 1
# endif

# ifdef __EMX__
# include <sys/types.h>
# include <sys/stat.h>
# endif

# if defined( OS_RHAPSODY ) || \
     defined( OS_MACOSX ) || \
     defined( OS_NEXT )
/* need unistd for rhapsody's proper lseek */
# include <sys/dir.h>
# include <unistd.h>
# define STRUCT_DIRENT struct direct
# else
# include <dirent.h>
# define STRUCT_DIRENT struct dirent
# endif

# ifdef OS_COHERENT
# include <arcoff.h>
# define HAVE_AR
# endif

# if defined( OS_MVS ) || \
         defined( OS_INTERIX )

#define ARMAG   "!<arch>\n"
#define SARMAG  8
#define ARFMAG  "`\n"

struct ar_hdr       /* archive file member header - printable ascii */
{
    char    ar_name[16];    /* file member name - `/' terminated */
    char    ar_date[12];    /* file member date - decimal */
    char    ar_uid[6];  /* file member user id - decimal */
    char    ar_gid[6];  /* file member group id - decimal */
    char    ar_mode[8]; /* file member mode - octal */
    char    ar_size[10];    /* file member size - decimal */
    char    ar_fmag[2]; /* ARFMAG - string to end header */
};

# define HAVE_AR
# endif

# if defined( OS_QNX ) || \
     defined( OS_BEOS ) || \
     defined( OS_MPEIX )
# define NO_AR
# define HAVE_AR
# endif

# ifndef HAVE_AR

# ifdef OS_AIX
/* Define those for AIX to get the definitions for both the small and the
 * big variant of the archive file format. */
#    define __AR_SMALL__
#    define __AR_BIG__
# endif

# include <ar.h>
# endif

/*
 * fileunix.c - manipulate file names and scan directories on UNIX/AmigaOS
 *
 * External routines:
 *
 *  file_dirscan() - scan a directory for files
 *  file_time() - get timestamp of file, if not done by file_dirscan()
 *  file_archscan() - scan an archive for files
 *
 * File_dirscan() and file_archscan() call back a caller provided function
 * for each file found.  A flag to this callback function lets file_dirscan()
 * and file_archscan() indicate that a timestamp is being provided with the
 * file.   If file_dirscan() or file_archscan() do not provide the file's
 * timestamp, interested parties may later call file_time().
 *
 * 04/08/94 (seiwald) - Coherent/386 support added.
 * 12/19/94 (mikem) - solaris string table insanity support
 * 02/14/95 (seiwald) - parse and build /xxx properly
 * 05/03/96 (seiwald) - split into pathunix.c
 * 11/21/96 (peterk) - BEOS does not have Unix-style archives
 */


/*
 * file_dirscan() - scan a directory for files.
 */

void file_dirscan( char * dir, scanback func, void * closure )
{
    PROFILE_ENTER( FILE_DIRSCAN );

    file_info_t * d = 0;

    d = file_query( dir );

    if ( !d || !d->is_dir )
    {
        PROFILE_EXIT( FILE_DIRSCAN );
        return;
    }

    if ( ! d->files )
    {
        LIST* files = L0;
        PATHNAME f;
        DIR *dd;
        STRUCT_DIRENT *dirent;
        string filename[1];

        /* First enter directory itself */

        memset( (char *)&f, '\0', sizeof( f ) );

        f.f_dir.ptr = dir;
        f.f_dir.len = strlen(dir);

        dir = *dir ? dir : ".";

        /* Now enter contents of directory. */

        if ( !( dd = opendir( dir ) ) )
        {
            PROFILE_EXIT( FILE_DIRSCAN );
            return;
        }

        if ( DEBUG_BINDSCAN )
            printf( "scan directory %s\n", dir );

        string_new( filename );
        while ( ( dirent = readdir( dd ) ) )
        {
            # ifdef old_sinix
            /* Broken structure definition on sinix. */
            f.f_base.ptr = dirent->d_name - 2;
            # else
            f.f_base.ptr = dirent->d_name;
            # endif
            f.f_base.len = strlen( f.f_base.ptr );

            string_truncate( filename, 0 );
            path_build( &f, filename, 0 );

            files = list_new( files, newstr(filename->value) );
            file_query( filename->value );
        }
        string_free( filename );

        closedir( dd );

        d->files = files;
    }

    /* Special case / : enter it */
    {
        unsigned long len = strlen(d->name);
        if ( ( len == 1 ) && ( d->name[0] == '/' ) )
            (*func)( closure, d->name, 1 /* stat()'ed */, d->time );
    }

    /* Now enter contents of directory */
    if ( d->files )
    {
        LIST * files = d->files;
        while ( files )
        {
            file_info_t * ff = file_info( files->string );
            (*func)( closure, ff->name, 1 /* stat()'ed */, ff->time );
            files = list_next( files );
        }
    }

    PROFILE_EXIT( FILE_DIRSCAN );
}


file_info_t * file_query( char * filename )
{
    file_info_t * ff = file_info( filename );
    if ( ! ff->time )
    {
        struct stat statbuf;

        if ( stat( *filename ? filename : ".", &statbuf ) < 0 )
            return 0;

        ff->is_file = statbuf.st_mode & S_IFREG ? 1 : 0;
        ff->is_dir = statbuf.st_mode & S_IFDIR ? 1 : 0;
        ff->size = statbuf.st_size;
        ff->time = statbuf.st_mtime ? statbuf.st_mtime : 1;
    }
    return ff;
}

/*
 * file_time() - get timestamp of file, if not done by file_dirscan()
 */

int
file_time(
    char    *filename,
    time_t  *time )
{
    file_info_t * ff = file_query( filename );
    if ( !ff ) return -1;
    *time = ff->time;
    return 0;
}

int file_is_file(char* filename)
{
    file_info_t * ff = file_query( filename );
    if ( !ff ) return -1;
    return ff->is_file;
}

int file_mkdir(char* pathname)
{
    return mkdir(pathname, 0766);
}

/*
 * file_archscan() - scan an archive for files
 */

# ifndef AIAMAG /* God-fearing UNIX */

# define SARFMAG 2
# define SARHDR sizeof( struct ar_hdr )

void
file_archscan(
    char *archive,
    scanback func,
    void *closure )
{
# ifndef NO_AR
    struct ar_hdr ar_hdr;
    char buf[ MAXJPATH ];
    long offset;
    char    *string_table = 0;
    int fd;

    if ( ( fd = open( archive, O_RDONLY, 0 ) ) < 0 )
        return;

    if ( read( fd, buf, SARMAG ) != SARMAG ||
        strncmp( ARMAG, buf, SARMAG ) )
    {
        close( fd );
        return;
    }

    offset = SARMAG;

    if ( DEBUG_BINDSCAN )
        printf( "scan archive %s\n", archive );

    while ( ( read( fd, &ar_hdr, SARHDR ) == SARHDR )
           && !( memcmp( ar_hdr.ar_fmag, ARFMAG, SARFMAG )
#ifdef ARFZMAG
              /* OSF also has a compressed format */
              && memcmp( ar_hdr.ar_fmag, ARFZMAG, SARFMAG )
#endif
          ) )
    {
        char   lar_name_[257];
        char * lar_name = lar_name_ + 1;
        long   lar_date;
        long   lar_size;
        long   lar_offset;
        char * c;
        char * src;
        char * dest;

        strncpy( lar_name, ar_hdr.ar_name, sizeof(ar_hdr.ar_name) );

        sscanf( ar_hdr.ar_date, "%ld", &lar_date );
        sscanf( ar_hdr.ar_size, "%ld", &lar_size );

        if (ar_hdr.ar_name[0] == '/')
        {
        if (ar_hdr.ar_name[1] == '/')
        {
            /* this is the "string table" entry of the symbol table,
            ** which holds strings of filenames that are longer than
            ** 15 characters (ie. don't fit into a ar_name
            */

            string_table = (char *)BJAM_MALLOC_ATOMIC(lar_size);
            lseek(fd, offset + SARHDR, 0);
            if (read(fd, string_table, lar_size) != lar_size)
            printf("error reading string table\n");
        }
        else if (string_table && ar_hdr.ar_name[1] != ' ')
        {
            /* Long filenames are recognized by "/nnnn" where nnnn is
            ** the offset of the string in the string table represented
            ** in ASCII decimals.
            */
            dest = lar_name;
            lar_offset = atoi(lar_name + 1);
            src = &string_table[lar_offset];
            while (*src != '/')
            *dest++ = *src++;
            *dest = '/';
        }
        }

        c = lar_name - 1;
        while ( ( *++c != ' ' ) && ( *c != '/' ) ) ;
        *c = '\0';

        if ( DEBUG_BINDSCAN )
        printf( "archive name %s found\n", lar_name );

        sprintf( buf, "%s(%s)", archive, lar_name );

        (*func)( closure, buf, 1 /* time valid */, (time_t)lar_date );

        offset += SARHDR + ( ( lar_size + 1 ) & ~1 );
        lseek( fd, offset, 0 );
    }

    if (string_table)
        BJAM_FREE(string_table);

    close( fd );

# endif /* NO_AR */

}

# else /* AIAMAG - RS6000 AIX */

static void file_archscan_small(
    int fd, char const *archive, scanback func, void *closure)
{
    struct fl_hdr fl_hdr;

    struct {
        struct ar_hdr hdr;
        char pad[ 256 ];
    } ar_hdr ;

    char buf[ MAXJPATH ];
    long offset;

    if ( read( fd, (char *)&fl_hdr, FL_HSZ ) != FL_HSZ)
        return;

    sscanf( fl_hdr.fl_fstmoff, "%ld", &offset );

    if ( DEBUG_BINDSCAN )
        printf( "scan archive %s\n", archive );

    while ( ( offset > 0 )
           && ( lseek( fd, offset, 0 ) >= 0 )
           && ( read( fd, &ar_hdr, sizeof( ar_hdr ) ) >= sizeof( ar_hdr.hdr ) ) )
    {
        long lar_date;
        int  lar_namlen;

        sscanf( ar_hdr.hdr.ar_namlen, "%d" , &lar_namlen );
        sscanf( ar_hdr.hdr.ar_date  , "%ld", &lar_date   );
        sscanf( ar_hdr.hdr.ar_nxtmem, "%ld", &offset     );

        if ( !lar_namlen )
            continue;

        ar_hdr.hdr._ar_name.ar_name[ lar_namlen ] = '\0';

        sprintf( buf, "%s(%s)", archive, ar_hdr.hdr._ar_name.ar_name );

        (*func)( closure, buf, 1 /* time valid */, (time_t)lar_date );
    }
}

/* Check for OS version which supports the big variant. */
#ifdef AR_HSZ_BIG

static void file_archscan_big(
    int fd, char const *archive, scanback func, void *closure)
{
    struct fl_hdr_big fl_hdr;

    struct {
        struct ar_hdr_big hdr;
        char pad[ 256 ];
    } ar_hdr ;

    char buf[ MAXJPATH ];
    long long offset;

    if ( read( fd, (char *)&fl_hdr, FL_HSZ_BIG) != FL_HSZ_BIG)
        return;

    sscanf( fl_hdr.fl_fstmoff, "%lld", &offset );

    if ( DEBUG_BINDSCAN )
        printf( "scan archive %s\n", archive );

    while ( ( offset > 0 )
           && ( lseek( fd, offset, 0 ) >= 0 )
           && ( read( fd, &ar_hdr, sizeof( ar_hdr ) ) >= sizeof( ar_hdr.hdr ) ) )
    {
        long lar_date;
        int  lar_namlen;

        sscanf( ar_hdr.hdr.ar_namlen, "%d"  , &lar_namlen );
        sscanf( ar_hdr.hdr.ar_date  , "%ld" , &lar_date   );
        sscanf( ar_hdr.hdr.ar_nxtmem, "%lld", &offset     );

        if ( !lar_namlen )
            continue;

        ar_hdr.hdr._ar_name.ar_name[ lar_namlen ] = '\0';

        sprintf( buf, "%s(%s)", archive, ar_hdr.hdr._ar_name.ar_name );

        (*func)( closure, buf, 1 /* time valid */, (time_t)lar_date );
    }

}

#endif /* AR_HSZ_BIG */

void file_archscan(char *archive, scanback func, void *closure)
{
    int fd;
    char fl_magic[SAIAMAG];

    if (( fd = open(archive, O_RDONLY, 0)) < 0)
        return;

    if (read( fd, fl_magic, SAIAMAG) != SAIAMAG
       || lseek(fd, 0, SEEK_SET) == -1)
    {
        close(fd);
        return;
    }

    if (strncmp(AIAMAG, fl_magic, SAIAMAG) == 0)
    {
        /* read small variant */
        file_archscan_small(fd, archive, func, closure);
    }
#ifdef AR_HSZ_BIG
    else if (strncmp(AIAMAGBIG, fl_magic, SAIAMAG) == 0)
    {
        /* read big variant */
        file_archscan_big(fd, archive, func, closure);
    }
#endif

    close( fd );
}

# endif /* AIAMAG - RS6000 AIX */

# endif /* USE_FILEUNIX */
