/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

#include <sys/syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <netatalk/endian.h>
#include <sys/param.h>
#include <atalk/afp.h>
#include <atalk/adouble.h>
#include <atalk/cnid.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

#include "desktop.h"
#include "directory.h"
#include "volume.h"
#include "globals.h"

struct dir *
adddir( vol, dir, name, namlen, upath, upathlen, st )
    struct vol	*vol;
    struct dir	*dir;
    char	*name, *upath;
    int		namlen, upathlen;
    struct stat *st;
{
    struct dir	*cdir;
#if AD_VERSION > AD_VERSION1
    struct adouble ad;
#endif

    if (( cdir = (struct dir *)malloc( sizeof( struct dir ))) == NULL ) {
	syslog( LOG_ERR, "adddir: malloc: %m" );
	return NULL;
    }
    cdir->d_left = NULL;
    cdir->d_right = NULL;
    cdir->d_parent = dir;
    cdir->d_child = NULL;
    if (( cdir->d_name = (char *)malloc( namlen + 1 )) == NULL ) {
	syslog( LOG_ERR, "adddir: malloc: %m" );
	free (cdir);
	return NULL;
    }
    strcpy( cdir->d_name, name );
    cdir->d_name[namlen] = '\0';

#if AD_VERSION > AD_VERSION1
    /* hmm. this is going to slow things down a little. as a result,
     * we do a lookup first. if that fails, we let cnid_add do its
     * work.  NOTE: it also does a lookup, but its lookup + addition
     * is protected by a lock. */
    if (!(cdir->d_did = cnid_lookup(vol->v_db, st, dir->d_did, upath,
				    upathlen))) {
      if (ad_open(upath, ADFLAGS_HF|ADFLAGS_DIR, O_RDONLY, 0, &ad) < 0)
	cdir->d_did = 0;
      else {
	memcpy(&cdir->d_did, ad_entry(&ad, ADEID_DID), sizeof(cdir->d_did));
	ad_close(&ad, ADFLAGS_HF);
      }
      
      if (!(cdir->d_did = cnid_add(vol->v_db, st, dir->d_did, upath, 
				   upathlen, cdir->d_did)))
	cdir->d_did = htonl( vol->v_lastdid++ );
    }
#else
      cdir->d_did = htonl( vol->v_lastdid++ );
#endif
    cdir->d_flags = 0;
    if ( dirinsert( vol, cdir ) < 0 ) {
        free(cdir->d_name);
	free(cdir);
	return( NULL );
    }

    cdir->d_next = dir->d_child;
    dir->d_child = cdir;

    return( cdir );
}

/*
 * Struct to save directory reading context in. Used to prevent
 * O(n^2) searches on a directory.
 */
struct savedir {
    u_short	sd_vid;
    int		sd_did;
    int		sd_buflen;
    char	*sd_buf;
    char	*sd_last;
    int		sd_sindex;
};
#define SDBUFBRK	1024

afp_enumerate(obj, ibuf, ibuflen, rbuf, rbuflen )
    AFPObj      *obj;
    char	*ibuf, *rbuf;
    int		ibuflen, *rbuflen;
{
    struct stat			st;
    static struct savedir	sd = { 0, 0, 0, NULL, NULL, 0 };
    struct vol			*vol;
    struct dir			*dir;
    struct dirent		*de;
    DIR				*dp;
    int				did, ret, esz, len;
    char			*path, *data, *end, *start;
    u_short			vid, fbitmap, dbitmap, reqcnt, actcnt = 0;
    u_short			sindex, maxsz, sz = 0;

    if ( sd.sd_buflen == 0 ) {
	if (( sd.sd_buf = (char *)malloc( SDBUFBRK )) == NULL ) {
	    syslog( LOG_ERR, "afp_enumerate: malloc: %m" );
	    *rbuflen = 0;
	    return AFPERR_MISC;
	}
	sd.sd_buflen = SDBUFBRK;
    }

    ibuf += 2;

    bcopy( ibuf, &vid, sizeof( u_short ));
    ibuf += sizeof( u_short );

    if (( vol = getvolbyvid( vid )) == NULL ) {
	*rbuflen = 0;
	return( AFPERR_PARAM );
    }

    bcopy( ibuf, &did, sizeof( int ));
    ibuf += sizeof( int );

    if (( dir = dirsearch( vol, did )) == NULL ) {
	*rbuflen = 0;
	return( AFPERR_NODIR );
    }

    bcopy( ibuf, &fbitmap, sizeof( u_short ));
    fbitmap = ntohs( fbitmap );
    ibuf += sizeof( u_short );

    bcopy( ibuf, &dbitmap, sizeof( u_short ));
    dbitmap = ntohs( dbitmap );
    ibuf += sizeof( u_short );

    bcopy( ibuf, &reqcnt, sizeof( u_short ));
    reqcnt = ntohs( reqcnt );
    ibuf += sizeof( u_short );

    bcopy( ibuf, &sindex, sizeof( u_short ));
    sindex = ntohs( sindex );
    ibuf += sizeof( u_short );

    bcopy( ibuf, &maxsz, sizeof( u_short ));
    maxsz = ntohs( maxsz );
    ibuf += sizeof( u_short );

    if (( path = cname( vol, dir, &ibuf )) == NULL ) {
	*rbuflen = 0;
	return( AFPERR_NODIR );
    }
    data = rbuf + 3 * sizeof( u_short );
    sz = 3 * sizeof( u_short );

    /*
     * Read the directory into a pre-malloced buffer, stored
     *		len <name> \0
     * The end is indicated by a len of 0.
     */
    if ( sindex == 1 || curdir->d_did != sd.sd_did || vid != sd.sd_vid ) {
	sd.sd_last = sd.sd_buf;

	if (( dp = opendir( mtoupath(vol, path ))) == NULL ) {
	    *rbuflen = 0;
	    return( AFPERR_NODIR );
	}

	end = sd.sd_buf + sd.sd_buflen;
	for ( de = readdir( dp ); de != NULL; de = readdir( dp )) {
	    if (!(validupath(vol, de->d_name)))
		continue;

	    len = strlen(de->d_name);
	    *(sd.sd_last)++ = len;

	    if ( sd.sd_last + len + 2 > end ) {
	        char *buf;

		start = sd.sd_buf;
		if ((buf = (char *) realloc( sd.sd_buf, sd.sd_buflen + 
					     SDBUFBRK )) == NULL ) {
		    syslog( LOG_ERR, "afp_enumerate: realloc: %m" );
		    *rbuflen = 0;
		    return AFPERR_MISC;
		}
		sd.sd_buf = buf;
		sd.sd_buflen += SDBUFBRK;
		sd.sd_last = ( sd.sd_last - start ) + sd.sd_buf;
		end = sd.sd_buf + sd.sd_buflen;
	    }

	    memcpy( sd.sd_last, de->d_name, len + 1 );
	    sd.sd_last += len + 1;
	}
	*sd.sd_last = 0;

	sd.sd_last = sd.sd_buf;
	sd.sd_sindex = 1;

	closedir( dp );
	sd.sd_vid = vid;
	sd.sd_did = did;
    }

    /*
     * Position sd_last as dictated by sindex.
     */
    if ( sindex < sd.sd_sindex ) {
	sd.sd_sindex = 1;
	sd.sd_last = sd.sd_buf;
    }
    while ( sd.sd_sindex < sindex ) {
	len = *(sd.sd_last)++;
	if ( len == 0 ) {
	    sd.sd_did = -1;	/* invalidate sd struct to force re-read */
	    *rbuflen = 0;
	    return( AFPERR_NOOBJ );
	}
	sd.sd_last += len + 1;
	sd.sd_sindex++;
    }

    while (( len = *(sd.sd_last)) != 0 ) {
	/*
	 * If we've got all we need, send it.
	 */
	if ( actcnt == reqcnt ) {
	    break;
	}

	/*
	 * Save the start position, in case we exceed the buffer
	 * limitation, and have to back up one.
	 */
	start = sd.sd_last;
	sd.sd_last++;

	if ( stat( sd.sd_last, &st ) < 0 ) {
	    syslog( LOG_DEBUG, "afp_enumerate: stat %s: %m", sd.sd_last );
	    sd.sd_last += len + 1;
	    continue;
	}

	/*
	 * If a fil/dir is not a dir, it's a file. This is slightly
	 * inaccurate, since that means /dev/null is a file, /dev/printer
	 * is a file, etc.
	 */
	if ( S_ISDIR(st.st_mode)) {
	    if ( dbitmap == 0 ) {
		sd.sd_last += len + 1;
		continue;
	    }
	    path = utompath(vol, sd.sd_last);

	    for ( dir = curdir->d_child; dir; dir = dir->d_next ) {
		if ( strcmp( dir->d_name, path ) == 0 ) {
		    break;
		}
	    }
	    if (!dir && ((dir = adddir( vol, curdir, path, strlen( path ),
					sd.sd_last, len, &st)) == NULL)) {
	      *rbuflen = 0;
	      return AFPERR_MISC;
	    }
	      

	    if (( ret = getdirparams(vol, dbitmap, sd.sd_last, dir,
		    &st, data + 2 * sizeof( u_char ), &esz )) != AFP_OK ) {
		*rbuflen = 0;
		return( ret );
	    }

	} else {
	    if ( fbitmap == 0 ) {
		sd.sd_last += len + 1;
		continue;
	    }
	    if (( ret = getfilparams(vol, fbitmap, utompath(vol, sd.sd_last),
		    curdir, &st, data + 2 * sizeof( u_char ), &esz )) !=
		    AFP_OK ) {
		*rbuflen = 0;
		return( ret );
	    }
	}

	/*
	 * Make sure entry is an even length, possibly with a null
	 * byte on the end.
	 */
	if ( esz & 1 ) {
	    *(data + 2 * sizeof( u_char ) + esz ) = '\0';
	    esz++;
	}

	/*
	 * Check if we've exceeded the size limit.
	 */
	if ( maxsz < sz + esz + 2 * sizeof( u_char )) {
	    sd.sd_last = start;
	    break;
	}

	sz += esz + 2 * sizeof( u_char );
	*data++ = esz + 2 * sizeof( u_char );
	if (S_ISDIR(st.st_mode)) {
	    *data++ = 1<<7;
	} else {
	    *data++ = 0;
	}
	data += esz;
	actcnt++;
	sd.sd_last += len + 1;
    }

    if ( actcnt == 0 ) {
	*rbuflen = 0;
	sd.sd_did = -1;		/* invalidate sd struct to force re-read */
	return( AFPERR_NOOBJ );
    }
    sd.sd_sindex = sindex + actcnt;

    /*
     * All done, fill in misc junk in rbuf
     */
    fbitmap = htons( fbitmap );
    bcopy( &fbitmap, rbuf, sizeof( u_short ));
    rbuf += sizeof( u_short );
    dbitmap = htons( dbitmap );
    bcopy( &dbitmap, rbuf, sizeof( u_short ));
    rbuf += sizeof( u_short );
    actcnt = htons( actcnt );
    bcopy( &actcnt, rbuf, sizeof( u_short ));
    rbuf += sizeof( u_short );
    *rbuflen = sz;
    return( AFP_OK );
}
